Merge "Merge chromium to 10.0.634.0: Compile fix"
diff --git a/Android.mk b/Android.mk
index 4a994ed..e3406ea 100644
--- a/Android.mk
+++ b/Android.mk
@@ -114,6 +114,7 @@
core/java/android/nfc/ILlcpConnectionlessSocket.aidl \
core/java/android/nfc/ILlcpServiceSocket.aidl \
core/java/android/nfc/ILlcpSocket.aidl \
+ core/java/android/nfc/INdefPushCallback.aidl \
core/java/android/nfc/INfcAdapter.aidl \
core/java/android/nfc/INfcAdapterExtras.aidl \
core/java/android/nfc/INfcTag.aidl \
@@ -161,6 +162,7 @@
core/java/com/android/internal/view/IInputMethodSession.aidl \
core/java/com/android/internal/widget/IRemoteViewsFactory.aidl \
core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl \
+ keystore/java/android/security/IKeyChainAliasResponse.aidl \
keystore/java/android/security/IKeyChainService.aidl \
location/java/android/location/ICountryDetector.aidl \
location/java/android/location/ICountryListener.aidl \
@@ -362,7 +364,7 @@
-werror -hide 113 \
-overview $(LOCAL_PATH)/core/java/overview.html
-framework_docs_LOCAL_ADDITIONAL_JAVA_DIR:= $(call intermediates-dir-for,JAVA_LIBRARIES,framework)
+framework_docs_LOCAL_ADDITIONAL_JAVA_DIR:= $(call intermediates-dir-for,JAVA_LIBRARIES,framework,,COMMON)
framework_docs_LOCAL_ADDITIONAL_DEPENDENCIES := \
frameworks/base/docs/knowntags.txt
@@ -585,7 +587,7 @@
LOCAL_MODULE_CLASS:=$(framework_docs_LOCAL_MODULE_CLASS)
LOCAL_DROIDDOC_SOURCE_PATH:=$(framework_docs_LOCAL_DROIDDOC_SOURCE_PATH)
LOCAL_DROIDDOC_HTML_DIR:=$(framework_docs_LOCAL_DROIDDOC_HTML_DIR)
-LOCAL_ADDITIONAL_JAVA_DIR:=$(call intermediates-dir-for,JAVA_LIBRARIES,framework)
+LOCAL_ADDITIONAL_JAVA_DIR:=$(framework_docs_LOCAL_ADDITIONAL_JAVA_DIR)
LOCAL_ADDITIONAL_DEPENDENCIES:=$(framework_docs_LOCAL_ADDITIONAL_DEPENDENCIES)
LOCAL_MODULE := hidden
diff --git a/api/current.txt b/api/current.txt
index 3339497..6854965 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -866,6 +866,7 @@
field public static final int subtitleTextStyle = 16843513; // 0x10102f9
field public static final int suggestActionMsg = 16843228; // 0x10101dc
field public static final int suggestActionMsgColumn = 16843229; // 0x10101dd
+ field public static final int suggestionsEnabled = 16843630; // 0x101036e
field public static final int summary = 16843241; // 0x10101e9
field public static final int summaryColumn = 16843426; // 0x10102a2
field public static final int summaryOff = 16843248; // 0x10101f0
@@ -1977,6 +1978,7 @@
method public boolean isRunning();
method public void removeChild(android.view.ViewGroup, android.view.View);
method public void removeTransitionListener(android.animation.LayoutTransition.TransitionListener);
+ method public void setAnimateParentHierarchy(boolean);
method public void setAnimator(int, android.animation.Animator);
method public void setDuration(long);
method public void setDuration(int, long);
@@ -6003,6 +6005,7 @@
field public static final int UI_MODE_TYPE_DESK = 2; // 0x2
field public static final int UI_MODE_TYPE_MASK = 15; // 0xf
field public static final int UI_MODE_TYPE_NORMAL = 1; // 0x1
+ field public static final int UI_MODE_TYPE_TELEVISION = 4; // 0x4
field public static final int UI_MODE_TYPE_UNDEFINED = 0; // 0x0
field public float fontScale;
field public int hardKeyboardHidden;
@@ -9684,7 +9687,8 @@
method public void unloadSoundEffects();
method public void unregisterMediaButtonEventReceiver(android.content.ComponentName);
field public static final java.lang.String ACTION_AUDIO_BECOMING_NOISY = "android.media.AUDIO_BECOMING_NOISY";
- field public static final java.lang.String ACTION_SCO_AUDIO_STATE_CHANGED = "android.media.SCO_AUDIO_STATE_CHANGED";
+ field public static final deprecated java.lang.String ACTION_SCO_AUDIO_STATE_CHANGED = "android.media.SCO_AUDIO_STATE_CHANGED";
+ field public static final java.lang.String ACTION_SCO_AUDIO_STATE_UPDATED = "android.media.ACTION_SCO_AUDIO_STATE_UPDATED";
field public static final int ADJUST_LOWER = -1; // 0xffffffff
field public static final int ADJUST_RAISE = 1; // 0x1
field public static final int ADJUST_SAME = 0; // 0x0
@@ -9697,6 +9701,7 @@
field public static final int AUDIOFOCUS_REQUEST_FAILED = 0; // 0x0
field public static final int AUDIOFOCUS_REQUEST_GRANTED = 1; // 0x1
field public static final java.lang.String EXTRA_RINGER_MODE = "android.media.EXTRA_RINGER_MODE";
+ field public static final java.lang.String EXTRA_SCO_AUDIO_PREVIOUS_STATE = "android.media.extra.SCO_AUDIO_PREVIOUS_STATE";
field public static final java.lang.String EXTRA_SCO_AUDIO_STATE = "android.media.extra.SCO_AUDIO_STATE";
field public static final java.lang.String EXTRA_VIBRATE_SETTING = "android.media.EXTRA_VIBRATE_SETTING";
field public static final java.lang.String EXTRA_VIBRATE_TYPE = "android.media.EXTRA_VIBRATE_TYPE";
@@ -9733,6 +9738,7 @@
field public static final deprecated int ROUTE_HEADSET = 8; // 0x8
field public static final deprecated int ROUTE_SPEAKER = 2; // 0x2
field public static final int SCO_AUDIO_STATE_CONNECTED = 1; // 0x1
+ field public static final int SCO_AUDIO_STATE_CONNECTING = 2; // 0x2
field public static final int SCO_AUDIO_STATE_DISCONNECTED = 0; // 0x0
field public static final int SCO_AUDIO_STATE_ERROR = -1; // 0xffffffff
field public static final int STREAM_ALARM = 4; // 0x4
@@ -11641,6 +11647,7 @@
method public void disableForegroundNdefPush(android.app.Activity);
method public void enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], java.lang.String[][]);
method public void enableForegroundNdefPush(android.app.Activity, android.nfc.NdefMessage);
+ method public void enableForegroundNdefPush(android.app.Activity, android.nfc.NfcAdapter.NdefPushCallback);
method public static android.nfc.NfcAdapter getDefaultAdapter(android.content.Context);
method public static deprecated android.nfc.NfcAdapter getDefaultAdapter();
method public boolean isEnabled();
@@ -11652,6 +11659,11 @@
field public static final java.lang.String EXTRA_TAG = "android.nfc.extra.TAG";
}
+ public static abstract interface NfcAdapter.NdefPushCallback {
+ method public abstract android.nfc.NdefMessage createMessage();
+ method public abstract void onMessagePushed();
+ }
+
public final class NfcManager {
method public android.nfc.NfcAdapter getDefaultAdapter();
}
@@ -21433,6 +21445,8 @@
method public void setScaleY(float);
method public void setScrollBarStyle(int);
method public void setScrollContainer(boolean);
+ method public void setScrollX(int);
+ method public void setScrollY(int);
method public void setScrollbarFadingEnabled(boolean);
method public void setSelected(boolean);
method public void setSoundEffectsEnabled(boolean);
@@ -21850,6 +21864,9 @@
public class ViewPropertyAnimator {
method public android.view.ViewPropertyAnimator alpha(float);
method public android.view.ViewPropertyAnimator alphaBy(float);
+ method public void cancel();
+ method public long getDuration();
+ method public long getStartDelay();
method public android.view.ViewPropertyAnimator rotation(float);
method public android.view.ViewPropertyAnimator rotationBy(float);
method public android.view.ViewPropertyAnimator rotationX(float);
@@ -21863,6 +21880,8 @@
method public android.view.ViewPropertyAnimator setDuration(long);
method public android.view.ViewPropertyAnimator setInterpolator(android.animation.TimeInterpolator);
method public android.view.ViewPropertyAnimator setListener(android.animation.Animator.AnimatorListener);
+ method public android.view.ViewPropertyAnimator setStartDelay(long);
+ method public void start();
method public android.view.ViewPropertyAnimator translationX(float);
method public android.view.ViewPropertyAnimator translationXBy(float);
method public android.view.ViewPropertyAnimator translationY(float);
@@ -25207,6 +25226,7 @@
method public final android.content.res.ColorStateList getLinkTextColors();
method public final boolean getLinksClickable();
method public final android.text.method.MovementMethod getMovementMethod();
+ method public int getOffsetForPosition(float, float);
method public android.text.TextPaint getPaint();
method public int getPaintFlags();
method public java.lang.String getPrivateImeOptions();
@@ -25227,6 +25247,7 @@
method public android.text.style.URLSpan[] getUrls();
method public boolean hasSelection();
method public boolean isInputMethodTarget();
+ method public boolean isSuggestionsEnabled();
method public boolean isTextSelectable();
method public int length();
method public boolean moveCursorToVisibleOffset();
@@ -25298,6 +25319,7 @@
method public void setSingleLine();
method public void setSingleLine(boolean);
method public final void setSpannableFactory(android.text.Spannable.Factory);
+ method public void setSuggestionsEnabled(boolean);
method public final void setText(java.lang.CharSequence);
method public void setText(java.lang.CharSequence, android.widget.TextView.BufferType);
method public final void setText(char[], int, int);
diff --git a/cmds/app_process/app_main.cpp b/cmds/app_process/app_main.cpp
index 371268f..152a7cb 100644
--- a/cmds/app_process/app_main.cpp
+++ b/cmds/app_process/app_main.cpp
@@ -149,10 +149,7 @@
mArgLen--;
AppRuntime runtime;
- const char *arg;
- const char *argv0;
-
- argv0 = argv[0];
+ const char* argv0 = argv[0];
// Process command line arguments
// ignore argv[0]
@@ -163,39 +160,53 @@
int i = runtime.addVmArguments(argc, argv);
- // Next arg is parent directory
- if (i < argc) {
- runtime.mParentDir = argv[i++];
+ // Parse runtime arguments. Stop at first unrecognized option.
+ bool zygote = false;
+ bool startSystemServer = false;
+ bool application = false;
+ const char* parentDir = NULL;
+ const char* niceName = NULL;
+ const char* className = NULL;
+ while (i < argc) {
+ const char* arg = argv[i++];
+ if (!parentDir) {
+ parentDir = arg;
+ } else if (strcmp(arg, "--zygote") == 0) {
+ zygote = true;
+ niceName = "zygote";
+ } else if (strcmp(arg, "--start-system-server") == 0) {
+ startSystemServer = true;
+ } else if (strcmp(arg, "--application") == 0) {
+ application = true;
+ } else if (strncmp(arg, "--nice-name=", 12) == 0) {
+ niceName = arg + 12;
+ } else {
+ className = arg;
+ break;
+ }
}
- // Next arg is startup classname or "--zygote"
- if (i < argc) {
- arg = argv[i++];
- if (0 == strcmp("--zygote", arg)) {
- bool startSystemServer = (i < argc) ?
- strcmp(argv[i], "--start-system-server") == 0 : false;
- setArgv0(argv0, "zygote");
- set_process_name("zygote");
- runtime.start("com.android.internal.os.ZygoteInit",
- startSystemServer);
- } else {
- set_process_name(argv0);
+ if (niceName && *niceName) {
+ setArgv0(argv0, niceName);
+ set_process_name(niceName);
+ }
- runtime.mClassName = arg;
+ runtime.mParentDir = parentDir;
- // Remainder of args get passed to startup class main()
- runtime.mArgC = argc-i;
- runtime.mArgV = argv+i;
-
- LOGV("App process is starting with pid=%d, class=%s.\n",
- getpid(), runtime.getClassName());
- runtime.start();
- }
+ if (zygote) {
+ runtime.start("com.android.internal.os.ZygoteInit",
+ startSystemServer ? "start-system-server" : "");
+ } else if (className) {
+ // Remainder of args get passed to startup class main()
+ runtime.mClassName = className;
+ runtime.mArgC = argc - i;
+ runtime.mArgV = argv + i;
+ runtime.start("com.android.internal.os.RuntimeInit",
+ application ? "application" : "tool");
} else {
fprintf(stderr, "Error: no class name or --zygote supplied.\n");
app_usage();
LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
return 10;
}
-
}
diff --git a/cmds/runtime/main_runtime.cpp b/cmds/runtime/main_runtime.cpp
index 785e4cc..dbff095 100644
--- a/cmds/runtime/main_runtime.cpp
+++ b/cmds/runtime/main_runtime.cpp
@@ -497,7 +497,7 @@
#ifndef HAVE_ANDROID_OS
QuickRuntime* runt = new QuickRuntime();
runt->start("com/android/server/SystemServer",
- false /* spontaneously fork system server from zygote */);
+ "" /* spontaneously fork system server from zygote */);
#endif
}
diff --git a/core/java/android/animation/LayoutTransition.java b/core/java/android/animation/LayoutTransition.java
index adfda8e..d25de97 100644
--- a/core/java/android/animation/LayoutTransition.java
+++ b/core/java/android/animation/LayoutTransition.java
@@ -18,6 +18,7 @@
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewParent;
import android.view.ViewTreeObserver;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
@@ -70,8 +71,9 @@
* moving as a result of the layout event) as well as the values that are changing (such as the
* position and size of that object). The actual values that are pushed to each animation
* depends on what properties are specified for the animation. For example, the default
- * CHANGE_APPEARING animation animates <code>left</code>, <code>top</code>, <code>right</code>,
- * and <code>bottom</code>. Values for these properties are updated with the pre- and post-layout
+ * CHANGE_APPEARING animation animates the <code>left</code>, <code>top</code>, <code>right</code>,
+ * <code>bottom</code>, <code>scrollX</code>, and <code>scrollY</code> properties.
+ * Values for these properties are updated with the pre- and post-layout
* values when the transition begins. Custom animations will be similarly populated with
* the target and values being animated, assuming they use ObjectAnimator objects with
* property names that are known on the target object.</p>
@@ -210,6 +212,14 @@
*/
private ArrayList<TransitionListener> mListeners;
+ /**
+ * Controls whether changing animations automatically animate the parent hierarchy as well.
+ * This behavior prevents artifacts when wrap_content layouts snap to the end state as the
+ * transition begins, causing visual glitches and clipping.
+ * Default value is true.
+ */
+ private boolean mAnimateParentHierarchy = true;
+
/**
* Constructs a LayoutTransition object. By default, the object will listen to layout
@@ -223,14 +233,17 @@
PropertyValuesHolder pvhTop = PropertyValuesHolder.ofInt("top", 0, 1);
PropertyValuesHolder pvhRight = PropertyValuesHolder.ofInt("right", 0, 1);
PropertyValuesHolder pvhBottom = PropertyValuesHolder.ofInt("bottom", 0, 1);
+ PropertyValuesHolder pvhScrollX = PropertyValuesHolder.ofInt("scrollX", 0, 1);
+ PropertyValuesHolder pvhScrollY = PropertyValuesHolder.ofInt("scrollY", 0, 1);
defaultChangeIn = ObjectAnimator.ofPropertyValuesHolder(this,
- pvhLeft, pvhTop, pvhRight, pvhBottom);
+ pvhLeft, pvhTop, pvhRight, pvhBottom, pvhScrollX, pvhScrollY);
defaultChangeIn.setDuration(DEFAULT_DURATION);
defaultChangeIn.setStartDelay(mChangingAppearingDelay);
defaultChangeIn.setInterpolator(mChangingAppearingInterpolator);
defaultChangeOut = defaultChangeIn.clone();
defaultChangeOut.setStartDelay(mChangingDisappearingDelay);
defaultChangeOut.setInterpolator(mChangingDisappearingInterpolator);
+
defaultFadeIn = ObjectAnimator.ofFloat(this, "alpha", 0f, 1f);
defaultFadeIn.setDuration(DEFAULT_DURATION);
defaultFadeIn.setStartDelay(mAppearingDelay);
@@ -572,122 +585,24 @@
// only animate the views not being added or removed
if (child != newView) {
-
-
- // Make a copy of the appropriate animation
- final Animator anim = baseAnimator.clone();
-
- // Set the target object for the animation
- anim.setTarget(child);
-
- // A ObjectAnimator (or AnimatorSet of them) can extract start values from
- // its target object
- anim.setupStartValues();
-
- // If there's an animation running on this view already, cancel it
- Animator currentAnimation = pendingAnimations.get(child);
- if (currentAnimation != null) {
- currentAnimation.cancel();
- pendingAnimations.remove(child);
- }
- // Cache the animation in case we need to cancel it later
- pendingAnimations.put(child, anim);
-
- // For the animations which don't get started, we have to have a means of
- // removing them from the cache, lest we leak them and their target objects.
- // We run an animator for the default duration+100 (an arbitrary time, but one
- // which should far surpass the delay between setting them up here and
- // handling layout events which start them.
- ValueAnimator pendingAnimRemover = ValueAnimator.ofFloat(0f, 1f).
- setDuration(duration+100);
- pendingAnimRemover.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- pendingAnimations.remove(child);
- }
- });
- pendingAnimRemover.start();
-
- // Add a listener to track layout changes on this view. If we don't get a callback,
- // then there's nothing to animate.
- final View.OnLayoutChangeListener listener = new View.OnLayoutChangeListener() {
- public void onLayoutChange(View v, int left, int top, int right, int bottom,
- int oldLeft, int oldTop, int oldRight, int oldBottom) {
-
- // Tell the animation to extract end values from the changed object
- anim.setupEndValues();
-
- long startDelay;
- if (changeReason == APPEARING) {
- startDelay = mChangingAppearingDelay + staggerDelay;
- staggerDelay += mChangingAppearingStagger;
- } else {
- startDelay = mChangingDisappearingDelay + staggerDelay;
- staggerDelay += mChangingDisappearingStagger;
- }
- anim.setStartDelay(startDelay);
- anim.setDuration(duration);
-
- Animator prevAnimation = currentChangingAnimations.get(child);
- if (prevAnimation != null) {
- prevAnimation.cancel();
- }
- Animator pendingAnimation = pendingAnimations.get(child);
- if (pendingAnimation != null) {
- pendingAnimations.remove(child);
- }
- // Cache the animation in case we need to cancel it later
- currentChangingAnimations.put(child, anim);
-
- if (anim instanceof ObjectAnimator) {
- ((ObjectAnimator) anim).setCurrentPlayTime(0);
- }
- anim.start();
-
- // this only removes listeners whose views changed - must clear the
- // other listeners later
- child.removeOnLayoutChangeListener(this);
- layoutChangeListenerMap.remove(child);
- }
- };
- // Remove the animation from the cache when it ends
- anim.addListener(new AnimatorListenerAdapter() {
-
- @Override
- public void onAnimationStart(Animator animator) {
- if (mListeners != null) {
- for (TransitionListener listener : mListeners) {
- listener.startTransition(LayoutTransition.this, parent, child,
- changeReason == APPEARING ?
- CHANGE_APPEARING : CHANGE_DISAPPEARING);
- }
- }
- }
-
- @Override
- public void onAnimationCancel(Animator animator) {
- child.removeOnLayoutChangeListener(listener);
- layoutChangeListenerMap.remove(child);
- }
-
- @Override
- public void onAnimationEnd(Animator animator) {
- currentChangingAnimations.remove(child);
- if (mListeners != null) {
- for (TransitionListener listener : mListeners) {
- listener.endTransition(LayoutTransition.this, parent, child,
- changeReason == APPEARING ?
- CHANGE_APPEARING : CHANGE_DISAPPEARING);
- }
- }
- }
- });
-
- child.addOnLayoutChangeListener(listener);
- // cache the listener for later removal
- layoutChangeListenerMap.put(child, listener);
+ setupChangeAnimation(parent, changeReason, baseAnimator, duration, child);
}
}
+ if (mAnimateParentHierarchy) {
+ ViewGroup tempParent = parent;
+ while (tempParent != null) {
+ ViewParent parentParent = tempParent.getParent();
+ if (parentParent instanceof ViewGroup) {
+ setupChangeAnimation((ViewGroup)parentParent, changeReason, baseAnimator,
+ duration, tempParent);
+ tempParent = (ViewGroup) parentParent;
+ } else {
+ tempParent = null;
+ }
+
+ }
+ }
+
// This is the cleanup step. When we get this rendering event, we know that all of
// the appropriate animations have been set up and run. Now we can clear out the
// layout listeners.
@@ -706,6 +621,175 @@
}
/**
+ * This flag controls whether CHANGE_APPEARING or CHANGE_DISAPPEARING animations will
+ * cause the same changing animation to be run on the parent hierarchy as well. This allows
+ * containers of transitioning views to also transition, which may be necessary in situations
+ * where the containers bounds change between the before/after states and may clip their
+ * children during the transition animations. For example, layouts with wrap_content will
+ * adjust their bounds according to the dimensions of their children.
+ *
+ * @param animateParentHierarchy A boolean value indicating whether the parents of
+ * transitioning views should also be animated during the transition. Default value is true.
+ */
+ public void setAnimateParentHierarchy(boolean animateParentHierarchy) {
+ mAnimateParentHierarchy = animateParentHierarchy;
+ }
+
+ /**
+ * Utility function called by runChangingTransition for both the children and the parent
+ * hierarchy.
+ */
+ private void setupChangeAnimation(final ViewGroup parent, final int changeReason,
+ Animator baseAnimator, final long duration, final View child) {
+ // Make a copy of the appropriate animation
+ final Animator anim = baseAnimator.clone();
+
+ // Set the target object for the animation
+ anim.setTarget(child);
+
+ // A ObjectAnimator (or AnimatorSet of them) can extract start values from
+ // its target object
+ anim.setupStartValues();
+
+ // If there's an animation running on this view already, cancel it
+ Animator currentAnimation = pendingAnimations.get(child);
+ if (currentAnimation != null) {
+ currentAnimation.cancel();
+ pendingAnimations.remove(child);
+ }
+ // Cache the animation in case we need to cancel it later
+ pendingAnimations.put(child, anim);
+
+ // For the animations which don't get started, we have to have a means of
+ // removing them from the cache, lest we leak them and their target objects.
+ // We run an animator for the default duration+100 (an arbitrary time, but one
+ // which should far surpass the delay between setting them up here and
+ // handling layout events which start them.
+ ValueAnimator pendingAnimRemover = ValueAnimator.ofFloat(0f, 1f).
+ setDuration(duration + 100);
+ pendingAnimRemover.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ pendingAnimations.remove(child);
+ }
+ });
+ pendingAnimRemover.start();
+
+ // Add a listener to track layout changes on this view. If we don't get a callback,
+ // then there's nothing to animate.
+ final View.OnLayoutChangeListener listener = new View.OnLayoutChangeListener() {
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft, int oldTop, int oldRight, int oldBottom) {
+
+ // Tell the animation to extract end values from the changed object
+ anim.setupEndValues();
+ if (anim instanceof ValueAnimator) {
+ boolean valuesDiffer = false;
+ ValueAnimator valueAnim = (ValueAnimator)anim;
+ PropertyValuesHolder[] oldValues = valueAnim.getValues();
+ for (int i = 0; i < oldValues.length; ++i) {
+ PropertyValuesHolder pvh = oldValues[i];
+ KeyframeSet keyframeSet = pvh.mKeyframeSet;
+ if (keyframeSet.mFirstKeyframe == null ||
+ keyframeSet.mLastKeyframe == null ||
+ !keyframeSet.mFirstKeyframe.getValue().equals(
+ keyframeSet.mLastKeyframe.getValue())) {
+ valuesDiffer = true;
+ }
+ }
+ if (!valuesDiffer) {
+ return;
+ }
+ }
+
+ long startDelay;
+ if (changeReason == APPEARING) {
+ startDelay = mChangingAppearingDelay + staggerDelay;
+ staggerDelay += mChangingAppearingStagger;
+ } else {
+ startDelay = mChangingDisappearingDelay + staggerDelay;
+ staggerDelay += mChangingDisappearingStagger;
+ }
+ anim.setStartDelay(startDelay);
+ anim.setDuration(duration);
+
+ Animator prevAnimation = currentChangingAnimations.get(child);
+ if (prevAnimation != null) {
+ prevAnimation.cancel();
+ }
+ Animator pendingAnimation = pendingAnimations.get(child);
+ if (pendingAnimation != null) {
+ pendingAnimations.remove(child);
+ }
+ // Cache the animation in case we need to cancel it later
+ currentChangingAnimations.put(child, anim);
+
+ parent.requestTransitionStart(LayoutTransition.this);
+
+ // this only removes listeners whose views changed - must clear the
+ // other listeners later
+ child.removeOnLayoutChangeListener(this);
+ layoutChangeListenerMap.remove(child);
+ }
+ };
+ // Remove the animation from the cache when it ends
+ anim.addListener(new AnimatorListenerAdapter() {
+
+ @Override
+ public void onAnimationStart(Animator animator) {
+ if (mListeners != null) {
+ for (TransitionListener listener : mListeners) {
+ listener.startTransition(LayoutTransition.this, parent, child,
+ changeReason == APPEARING ?
+ CHANGE_APPEARING : CHANGE_DISAPPEARING);
+ }
+ }
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animator) {
+ child.removeOnLayoutChangeListener(listener);
+ layoutChangeListenerMap.remove(child);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ currentChangingAnimations.remove(child);
+ if (mListeners != null) {
+ for (TransitionListener listener : mListeners) {
+ listener.endTransition(LayoutTransition.this, parent, child,
+ changeReason == APPEARING ?
+ CHANGE_APPEARING : CHANGE_DISAPPEARING);
+ }
+ }
+ }
+ });
+
+ child.addOnLayoutChangeListener(listener);
+ // cache the listener for later removal
+ layoutChangeListenerMap.put(child, listener);
+ }
+
+ /**
+ * Starts the animations set up for a CHANGING transition. We separate the setup of these
+ * animations from actually starting them, to avoid side-effects that starting the animations
+ * may have on the properties of the affected objects. After setup, we tell the affected parent
+ * that this transition should be started. The parent informs its ViewAncestor, which then
+ * starts the transition after the current layout/measurement phase, just prior to drawing
+ * the view hierarchy.
+ *
+ * @hide
+ */
+ public void startChangingAnimations() {
+ for (Animator anim : currentChangingAnimations.values()) {
+ if (anim instanceof ObjectAnimator) {
+ ((ObjectAnimator) anim).setCurrentPlayTime(0);
+ }
+ anim.start();
+ }
+ }
+
+ /**
* Returns true if animations are running which animate layout-related properties. This
* essentially means that either CHANGE_APPEARING or CHANGE_DISAPPEARING animations
* are running, since these animations operate on layout-related properties.
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 87369ab..3877bd0 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1398,6 +1398,10 @@
public void onConfigurationChanged(Configuration newConfig) {
mCalled = true;
+ if (mActionBar != null) {
+ mActionBar.onConfigurationChanged(newConfig);
+ }
+
mFragments.dispatchConfigurationChanged(newConfig);
if (mWindow != null) {
diff --git a/core/java/android/app/IThumbnailRetriever.aidl b/core/java/android/app/IThumbnailRetriever.aidl
index 2a6737d..410cc20 100644
--- a/core/java/android/app/IThumbnailRetriever.aidl
+++ b/core/java/android/app/IThumbnailRetriever.aidl
@@ -18,6 +18,7 @@
/**
* System private API for retrieving thumbnails
+ * {@hide}
*/
interface IThumbnailRetriever {
Bitmap getThumbnail(int index);
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index 95451d6..71f6445 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -167,7 +167,8 @@
* Return the current running mode type. May be one of
* {@link Configuration#UI_MODE_TYPE_NORMAL Configuration.UI_MODE_TYPE_NORMAL},
* {@link Configuration#UI_MODE_TYPE_DESK Configuration.UI_MODE_TYPE_DESK}, or
- * {@link Configuration#UI_MODE_TYPE_CAR Configuration.UI_MODE_TYPE_CAR},
+ * {@link Configuration#UI_MODE_TYPE_CAR Configuration.UI_MODE_TYPE_CAR}, or
+ * {@link Configuration#UI_MODE_TYPE_TELEVISION Configuration.UI_MODE_TYPE_TV}.
*/
public int getCurrentModeType() {
if (mService != null) {
diff --git a/core/java/android/content/IOnPrimaryClipChangedListener.aidl b/core/java/android/content/IOnPrimaryClipChangedListener.aidl
index fb42a45..46d7f7c 100644
--- a/core/java/android/content/IOnPrimaryClipChangedListener.aidl
+++ b/core/java/android/content/IOnPrimaryClipChangedListener.aidl
@@ -16,6 +16,9 @@
package android.content;
+/**
+ * {@hide}
+ */
oneway interface IOnPrimaryClipChangedListener {
void dispatchPrimaryClipChanged();
}
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 12ec258..51a7115 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -226,6 +226,7 @@
public static final int UI_MODE_TYPE_NORMAL = 0x01;
public static final int UI_MODE_TYPE_DESK = 0x02;
public static final int UI_MODE_TYPE_CAR = 0x03;
+ public static final int UI_MODE_TYPE_TELEVISION = 0x04;
public static final int UI_MODE_NIGHT_MASK = 0x30;
public static final int UI_MODE_NIGHT_UNDEFINED = 0x00;
@@ -367,6 +368,7 @@
case UI_MODE_TYPE_NORMAL: /* normal is not interesting to print */ break;
case UI_MODE_TYPE_DESK: sb.append(" desk"); break;
case UI_MODE_TYPE_CAR: sb.append(" car"); break;
+ case UI_MODE_TYPE_TELEVISION: sb.append(" television"); break;
default: sb.append(" uimode="); sb.append(uiMode&UI_MODE_TYPE_MASK); break;
}
switch ((uiMode&UI_MODE_NIGHT_MASK)) {
diff --git a/core/java/android/nfc/INdefPushCallback.aidl b/core/java/android/nfc/INdefPushCallback.aidl
new file mode 100644
index 0000000..80ba2ed
--- /dev/null
+++ b/core/java/android/nfc/INdefPushCallback.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nfc;
+
+import android.nfc.NdefMessage;
+
+/**
+ * @hide
+ */
+interface INdefPushCallback
+{
+ NdefMessage onConnect();
+ void onMessagePushed();
+}
diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl
index 870127c..d11fea0 100644
--- a/core/java/android/nfc/INfcAdapter.aidl
+++ b/core/java/android/nfc/INfcAdapter.aidl
@@ -25,6 +25,7 @@
import android.nfc.ILlcpSocket;
import android.nfc.ILlcpServiceSocket;
import android.nfc.ILlcpConnectionlessSocket;
+import android.nfc.INdefPushCallback;
import android.nfc.INfcTag;
import android.nfc.IP2pTarget;
import android.nfc.IP2pInitiator;
@@ -51,6 +52,7 @@
in IntentFilter[] filters, in TechListParcel techLists);
void disableForegroundDispatch(in ComponentName activity);
void enableForegroundNdefPush(in ComponentName activity, in NdefMessage msg);
+ void enableForegroundNdefPushWithCallback(in ComponentName activity, in INdefPushCallback callback);
void disableForegroundNdefPush(in ComponentName activity);
// Non-public methods
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index 4689804..738e75f 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -124,7 +124,7 @@
* Intent to start an activity when a tag is discovered.
*
* <p>This intent will not be started when a tag is discovered if any activities respond to
- * {@link #ACTION_NDEF_DISCOVERED} or {@link #ACTION_TECH_DISCOVERED} for the current tag.
+ * {@link #ACTION_NDEF_DISCOVERED} or {@link #ACTION_TECH_DISCOVERED} for the current tag.
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_TAG_DISCOVERED = "android.nfc.action.TAG_DISCOVERED";
@@ -235,6 +235,37 @@
*/
private static final int DISCOVERY_MODE_CARD_EMULATION = 2;
+ /**
+ * Callback passed into {@link #enableForegroundNdefPush(Activity,NdefPushCallback)}. This
+ */
+ public interface NdefPushCallback {
+ /**
+ * Called when a P2P connection is created.
+ */
+ NdefMessage createMessage();
+ /**
+ * Called when the message is pushed.
+ */
+ void onMessagePushed();
+ }
+
+ private static class NdefPushCallbackWrapper extends INdefPushCallback.Stub {
+ private NdefPushCallback mCallback;
+
+ public NdefPushCallbackWrapper(NdefPushCallback callback) {
+ mCallback = callback;
+ }
+
+ @Override
+ public NdefMessage onConnect() {
+ return mCallback.createMessage();
+ }
+
+ @Override
+ public void onMessagePushed() {
+ mCallback.onMessagePushed();
+ }
+ }
// Guarded by NfcAdapter.class
private static boolean sIsInitialized = false;
@@ -575,6 +606,44 @@
}
/**
+ * Enable NDEF message push over P2P while this Activity is in the foreground.
+ *
+ * <p>For this to function properly the other NFC device being scanned must
+ * support the "com.android.npp" NDEF push protocol. Support for this
+ * protocol is currently optional for Android NFC devices.
+ *
+ * <p>This method must be called from the main thread.
+ *
+ * <p class="note"><em>NOTE:</em> While foreground NDEF push is active standard tag dispatch is disabled.
+ * Only the foreground activity may receive tag discovered dispatches via
+ * {@link #enableForegroundDispatch}.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @param activity the foreground Activity
+ * @param callback is called on when the P2P connection is established
+ * @throws IllegalStateException if the Activity is not currently in the foreground
+ * @throws OperationNotSupportedException if this Android device does not support NDEF push
+ */
+ public void enableForegroundNdefPush(Activity activity, NdefPushCallback callback) {
+ if (activity == null || callback == null) {
+ throw new NullPointerException();
+ }
+ if (!activity.isResumed()) {
+ throw new IllegalStateException("Foregorund NDEF push can only be enabled " +
+ "when your activity is resumed");
+ }
+ try {
+ ActivityThread.currentActivityThread().registerOnActivityPausedListener(activity,
+ mForegroundNdefPushListener);
+ sService.enableForegroundNdefPushWithCallback(activity.getComponentName(),
+ new NdefPushCallbackWrapper(callback));
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ }
+ }
+
+ /**
* Disable NDEF message push over P2P.
*
* <p>After calling {@link #enableForegroundNdefPush}, an activity
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index f85df6c..d475f36 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -634,6 +634,20 @@
}
/**
+ * Returns the parent process id for a currently running process.
+ * @param pid the process id
+ * @return the parent process id of the process, or -1 if the process is not running.
+ * @hide
+ */
+ public static final int getParentPid(int pid) {
+ String[] procStatusLabels = { "PPid:" };
+ long[] procStatusValues = new long[1];
+ procStatusValues[0] = -1;
+ Process.readProcLines("/proc/" + pid + "/status", procStatusLabels, procStatusValues);
+ return (int) procStatusValues[0];
+ }
+
+ /**
* Set the priority of a thread, based on Linux priorities.
*
* @param tid The identifier of the thread/process to change.
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index d68e6fb..bc6e993 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -36,6 +36,11 @@
private final int mMtpReserveSpace;
private int mStorageId;
+ // StorageVolume extra for ACTION_MEDIA_REMOVED, ACTION_MEDIA_UNMOUNTED, ACTION_MEDIA_CHECKING,
+ // ACTION_MEDIA_NOFS, ACTION_MEDIA_MOUNTED, ACTION_MEDIA_SHARED, ACTION_MEDIA_UNSHARED,
+ // ACTION_MEDIA_BAD_REMOVAL, ACTION_MEDIA_UNMOUNTABLE and ACTION_MEDIA_EJECT broadcasts.
+ public static final String EXTRA_STORAGE_VOLUME = "storage_volume";
+
public StorageVolume(String path, String description,
boolean removable, boolean emulated, int mtpReserveSpace) {
mPath = path;
diff --git a/core/java/android/provider/Calendar.java b/core/java/android/provider/Calendar.java
index 4141879..20614dc 100644
--- a/core/java/android/provider/Calendar.java
+++ b/core/java/android/provider/Calendar.java
@@ -616,18 +616,6 @@
public static final String DTEND = "dtend";
/**
- * The time the event starts with allDay events in a local tz
- * <P>Type: INTEGER (long; millis since epoch)</P>
- */
- public static final String DTSTART2 = "dtstart2";
-
- /**
- * The time the event ends with allDay events in a local tz
- * <P>Type: INTEGER (long; millis since epoch)</P>
- */
- public static final String DTEND2 = "dtend2";
-
- /**
* The duration of the event
* <P>Type: TEXT (duration in RFC2445 format)</P>
*/
@@ -734,8 +722,16 @@
public static final String EXDATE = "exdate";
/**
+ * The _id of the original recurring event for which this event is an
+ * exception.
+ * <P>Type: TEXT</P>
+ */
+ public static final String ORIGINAL_ID = "original_id";
+
+ /**
* The _sync_id of the original recurring event for which this event is
- * an exception.
+ * an exception. The provider should keep the original_id in sync when
+ * this is updated.
* <P>Type: TEXT</P>
*/
public static final String ORIGINAL_SYNC_ID = "original_sync_id";
@@ -899,6 +895,7 @@
DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, DTEND);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, DURATION);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, EVENT_TIMEZONE);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, EVENT_END_TIMEZONE);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, ALL_DAY);
DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, ACCESS_LEVEL);
DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, AVAILABILITY);
@@ -910,6 +907,7 @@
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, EXRULE);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, EXDATE);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, ORIGINAL_SYNC_ID);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, ORIGINAL_ID);
DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv,
ORIGINAL_INSTANCE_TIME);
DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, ORIGINAL_ALL_DAY);
@@ -925,7 +923,7 @@
DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, DIRTY);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_VERSION);
DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, EventsColumns.DELETED);
- DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, Calendars.SYNC1);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SYNC1);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv,
Events.SYNC_DATA1);
diff --git a/core/java/android/speech/tts/PlaybackSynthesisRequest.java b/core/java/android/speech/tts/PlaybackSynthesisRequest.java
index 6f4c15b..d698b54 100644
--- a/core/java/android/speech/tts/PlaybackSynthesisRequest.java
+++ b/core/java/android/speech/tts/PlaybackSynthesisRequest.java
@@ -18,6 +18,7 @@
import android.media.AudioFormat;
import android.media.AudioTrack;
import android.os.Bundle;
+import android.os.Handler;
import android.util.Log;
/**
@@ -49,16 +50,20 @@
private final float mPan;
private final Object mStateLock = new Object();
- private AudioTrack mAudioTrack = null;
+ private final Handler mAudioTrackHandler;
+ private volatile AudioTrack mAudioTrack = null;
private boolean mStopped = false;
private boolean mDone = false;
+ private volatile boolean mWriteErrorOccured;
PlaybackSynthesisRequest(String text, Bundle params,
- int streamType, float volume, float pan) {
+ int streamType, float volume, float pan, Handler audioTrackHandler) {
super(text, params);
mStreamType = streamType;
mVolume = volume;
mPan = pan;
+ mAudioTrackHandler = audioTrackHandler;
+ mWriteErrorOccured = false;
}
@Override
@@ -70,14 +75,28 @@
}
}
+ // Always guarded by mStateLock.
private void cleanUp() {
if (DBG) Log.d(TAG, "cleanUp()");
- if (mAudioTrack != null) {
- mAudioTrack.flush();
- mAudioTrack.stop();
- mAudioTrack.release();
- mAudioTrack = null;
+ if (mAudioTrack == null) {
+ return;
}
+
+ final AudioTrack audioTrack = mAudioTrack;
+ mAudioTrack = null;
+
+ // Clean up on the audiotrack handler thread.
+ //
+ // NOTE: It isn't very clear whether AudioTrack is thread safe.
+ // If it is we can clean up on the current (synthesis) thread.
+ mAudioTrackHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ audioTrack.flush();
+ audioTrack.stop();
+ audioTrack.release();
+ }
+ });
}
@Override
@@ -146,10 +165,15 @@
Log.d(TAG, "audioAvailable(byte[" + buffer.length + "],"
+ offset + "," + length + ")");
}
- if (length > getMaxBufferSize()) {
- throw new IllegalArgumentException("buffer is too large (" + length + " bytes)");
+ if (length > getMaxBufferSize() || length <= 0) {
+ throw new IllegalArgumentException("buffer is too large or of zero length (" +
+ + length + " bytes)");
}
synchronized (mStateLock) {
+ if (mWriteErrorOccured) {
+ if (DBG) Log.d(TAG, "Error writing to audio track, count < 0");
+ return TextToSpeech.ERROR;
+ }
if (mStopped) {
if (DBG) Log.d(TAG, "Request has been aborted.");
return TextToSpeech.ERROR;
@@ -158,22 +182,33 @@
Log.e(TAG, "audioAvailable(): Not started");
return TextToSpeech.ERROR;
}
- int playState = mAudioTrack.getPlayState();
- if (playState == AudioTrack.PLAYSTATE_STOPPED) {
- if (DBG) Log.d(TAG, "AudioTrack stopped, restarting");
- mAudioTrack.play();
- }
- // TODO: loop until all data is written?
- if (DBG) Log.d(TAG, "AudioTrack.write()");
- int count = mAudioTrack.write(buffer, offset, length);
- if (DBG) Log.d(TAG, "AudioTrack.write() returned " + count);
- if (count < 0) {
- Log.e(TAG, "Writing to AudioTrack failed: " + count);
- cleanUp();
- return TextToSpeech.ERROR;
- } else {
- return TextToSpeech.SUCCESS;
- }
+ final AudioTrack audioTrack = mAudioTrack;
+ // Sigh, another copy.
+ final byte[] bufferCopy = new byte[length];
+ System.arraycopy(buffer, offset, bufferCopy, 0, length);
+
+ mAudioTrackHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ int playState = audioTrack.getPlayState();
+ if (playState == AudioTrack.PLAYSTATE_STOPPED) {
+ if (DBG) Log.d(TAG, "AudioTrack stopped, restarting");
+ audioTrack.play();
+ }
+ // TODO: loop until all data is written?
+ if (DBG) Log.d(TAG, "AudioTrack.write()");
+ int count = audioTrack.write(bufferCopy, 0, bufferCopy.length);
+ // The semantics of this change very slightly. Earlier, we would
+ // report an error immediately, Now we will return an error on
+ // the next API call, usually done( ) or another audioAvailable( )
+ // call.
+ if (count < 0) {
+ mWriteErrorOccured = true;
+ }
+ }
+ });
+
+ return TextToSpeech.SUCCESS;
}
}
@@ -181,6 +216,10 @@
public int done() {
if (DBG) Log.d(TAG, "done()");
synchronized (mStateLock) {
+ if (mWriteErrorOccured) {
+ if (DBG) Log.d(TAG, "Error writing to audio track, count < 0");
+ return TextToSpeech.ERROR;
+ }
if (mStopped) {
if (DBG) Log.d(TAG, "Request has been aborted.");
return TextToSpeech.ERROR;
diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java
index f32474f..717dde8 100644
--- a/core/java/android/speech/tts/TextToSpeechService.java
+++ b/core/java/android/speech/tts/TextToSpeechService.java
@@ -51,6 +51,7 @@
private static final String SYNTH_THREAD_NAME = "SynthThread";
private SynthHandler mSynthHandler;
+ private Handler mAudioTrackHandler;
private CallbackMap mCallbacks;
@@ -63,6 +64,10 @@
synthThread.start();
mSynthHandler = new SynthHandler(synthThread.getLooper());
+ HandlerThread audioTrackThread = new HandlerThread("TTS.audioTrackThread");
+ audioTrackThread.start();
+ mAudioTrackHandler = new Handler(audioTrackThread.getLooper());
+
mCallbacks = new CallbackMap();
// Load default language
@@ -75,6 +80,7 @@
// Tell the synthesizer to stop
mSynthHandler.quit();
+ mAudioTrackHandler.getLooper().quit();
// Unregister all callbacks.
mCallbacks.kill();
@@ -444,7 +450,7 @@
protected SynthesisRequest createSynthesisRequest() {
return new PlaybackSynthesisRequest(mText, mParams,
- getStreamType(), getVolume(), getPan());
+ getStreamType(), getVolume(), getPan(), mAudioTrackHandler);
}
private void setRequestParams(SynthesisRequest request) {
diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java
index d432dee..fe96565 100644
--- a/core/java/android/text/method/ArrowKeyMovementMethod.java
+++ b/core/java/android/text/method/ArrowKeyMovementMethod.java
@@ -234,7 +234,7 @@
if (action == MotionEvent.ACTION_DOWN) {
boolean cap = isSelecting(buffer);
if (cap) {
- int offset = widget.getOffset((int) event.getX(), (int) event.getY());
+ int offset = widget.getOffsetForPosition(event.getX(), event.getY());
buffer.setSpan(LAST_TAP_DOWN, offset, offset, Spannable.SPAN_POINT_POINT);
@@ -259,7 +259,7 @@
// Update selection as we're moving the selection area.
// Get the current touch position
- int offset = widget.getOffset((int) event.getX(), (int) event.getY());
+ int offset = widget.getOffsetForPosition(event.getX(), event.getY());
Selection.extendSelection(buffer, offset);
return true;
@@ -275,7 +275,7 @@
return true;
}
- int offset = widget.getOffset((int) event.getX(), (int) event.getY());
+ int offset = widget.getOffsetForPosition(event.getX(), event.getY());
if (isSelecting(buffer)) {
buffer.removeSpan(LAST_TAP_DOWN);
Selection.extendSelection(buffer, offset);
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index 845fbc3..61a24a0 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -17,7 +17,6 @@
package android.view;
-import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
@@ -137,14 +136,14 @@
*
* @param canvas The Canvas used to render the view.
*/
- void onHardwarePreDraw(Canvas canvas);
+ void onHardwarePreDraw(HardwareCanvas canvas);
/**
* Invoked after a view is drawn by a hardware renderer.
*
* @param canvas The Canvas used to render the view.
*/
- void onHardwarePostDraw(Canvas canvas);
+ void onHardwarePostDraw(HardwareCanvas canvas);
}
/**
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 82fd581..3436cd1 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -883,8 +883,8 @@
* <p>
* <ul>
* <li>For a stylus, reports the distance of the stylus from the screen.
- * The value is normalized to a range from 0.0 (direct contact) to 1.0 (furthest measurable
- * distance).
+ * The value is nominally measured in millimeters where 0.0 indicates direct contact
+ * and larger values indicate increasing distance from the surface.
* </ul>
* </p>
*
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index d5f573c..98d07c4 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -6048,6 +6048,26 @@
}
/**
+ * Set the horizontal scrolled position of your view. This will cause a call to
+ * {@link #onScrollChanged(int, int, int, int)} and the view will be
+ * invalidated.
+ * @param value the x position to scroll to
+ */
+ public void setScrollX(int value) {
+ scrollTo(value, mScrollY);
+ }
+
+ /**
+ * Set the vertical scrolled position of your view. This will cause a call to
+ * {@link #onScrollChanged(int, int, int, int)} and the view will be
+ * invalidated.
+ * @param value the y position to scroll to
+ */
+ public void setScrollY(int value) {
+ scrollTo(mScrollX, value);
+ }
+
+ /**
* Return the scrolled left position of this view. This is the left edge of
* the displayed part of your view. You do not need to draw any pixels
* farther left, since those are outside of the frame of your view on
diff --git a/core/java/android/view/ViewAncestor.java b/core/java/android/view/ViewAncestor.java
index 8085ea8..bf04502 100644
--- a/core/java/android/view/ViewAncestor.java
+++ b/core/java/android/view/ViewAncestor.java
@@ -17,6 +17,7 @@
package android.view;
import android.Manifest;
+import android.animation.LayoutTransition;
import android.app.ActivityManagerNative;
import android.content.ClipDescription;
import android.content.ComponentCallbacks;
@@ -25,7 +26,6 @@
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PixelFormat;
@@ -45,9 +45,7 @@
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.SystemClock;
-import android.os.SystemProperties;
import android.util.AndroidRuntimeException;
import android.util.DisplayMetrics;
import android.util.EventLog;
@@ -230,10 +228,11 @@
int mScrollY;
int mCurScrollY;
Scroller mScroller;
- Bitmap mResizeBitmap;
- long mResizeBitmapStartTime;
- int mResizeBitmapDuration;
+ HardwareLayer mResizeBuffer;
+ long mResizeBufferStartTime;
+ int mResizeBufferDuration;
static final Interpolator mResizeInterpolator = new AccelerateDecelerateInterpolator();
+ private ArrayList<LayoutTransition> mPendingTransitions;
final ViewConfiguration mViewConfiguration;
@@ -672,6 +671,7 @@
if (!mTraversalScheduled) {
mTraversalScheduled = true;
+ //noinspection ConstantConditions
if (ViewDebug.DEBUG_LATENCY && mLastTraversalFinishedTimeNanos != 0) {
final long now = System.nanoTime();
Log.d(TAG, "Latency: Scheduled traversal, it has been "
@@ -694,10 +694,32 @@
return mAppVisible ? mView.getVisibility() : View.GONE;
}
- void disposeResizeBitmap() {
- if (mResizeBitmap != null) {
- mResizeBitmap.recycle();
- mResizeBitmap = null;
+ void disposeResizeBuffer() {
+ if (mResizeBuffer != null) {
+ mResizeBuffer.destroy();
+ mResizeBuffer = null;
+ }
+ }
+
+ /**
+ * Add LayoutTransition to the list of transitions to be started in the next traversal.
+ * This list will be cleared after the transitions on the list are start()'ed. These
+ * transitionsa re added by LayoutTransition itself when it sets up animations. The setup
+ * happens during the layout phase of traversal, which we want to complete before any of the
+ * animations are started (because those animations may side-effect properties that layout
+ * depends upon, like the bounding rectangles of the affected views). So we add the transition
+ * to the list and it is started just prior to starting the drawing phase of traversal.
+ *
+ * @param transition The LayoutTransition to be started on the next traversal.
+ *
+ * @hide
+ */
+ public void requestTransitionStart(LayoutTransition transition) {
+ if (mPendingTransitions == null || !mPendingTransitions.contains(transition)) {
+ if (mPendingTransitions == null) {
+ mPendingTransitions = new ArrayList<LayoutTransition>();
+ }
+ mPendingTransitions.add(transition);
}
}
@@ -819,15 +841,25 @@
mAttachInfo.mHardwareRenderer.isEnabled() &&
lp != null && !PixelFormat.formatHasAlpha(lp.format)) {
- disposeResizeBitmap();
+ disposeResizeBuffer();
boolean completed = false;
+ HardwareCanvas canvas = null;
try {
- mResizeBitmap = Bitmap.createBitmap(mWidth, mHeight,
- Bitmap.Config.ARGB_8888);
- mResizeBitmap.setHasAlpha(false);
- Canvas canvas = new Canvas(mResizeBitmap);
+ if (mResizeBuffer == null) {
+ mResizeBuffer = mAttachInfo.mHardwareRenderer.createHardwareLayer(
+ mWidth, mHeight, false);
+ } else if (mResizeBuffer.getWidth() != mWidth ||
+ mResizeBuffer.getHeight() != mHeight) {
+ mResizeBuffer.resize(mWidth, mHeight);
+ }
+ canvas = mResizeBuffer.start(mAttachInfo.mHardwareCanvas);
+ canvas.setViewport(mWidth, mHeight);
+ canvas.onPreDraw(null);
+ final int restoreCount = canvas.save();
+
canvas.drawColor(0xff000000, PorterDuff.Mode.SRC);
+
int yoff;
final boolean scrolling = mScroller != null
&& mScroller.computeScrollOffset();
@@ -837,22 +869,32 @@
} else {
yoff = mScrollY;
}
+
canvas.translate(0, -yoff);
if (mTranslator != null) {
mTranslator.translateCanvas(canvas);
}
- canvas.setScreenDensity(mAttachInfo.mScalingRequired
- ? DisplayMetrics.DENSITY_DEVICE : 0);
+
mView.draw(canvas);
- mResizeBitmapStartTime = SystemClock.uptimeMillis();
- mResizeBitmapDuration = mView.getResources().getInteger(
+
+ mResizeBufferStartTime = SystemClock.uptimeMillis();
+ mResizeBufferDuration = mView.getResources().getInteger(
com.android.internal.R.integer.config_mediumAnimTime);
completed = true;
+
+ canvas.restoreToCount(restoreCount);
} catch (OutOfMemoryError e) {
Log.w(TAG, "Not enough memory for content change anim buffer", e);
} finally {
- if (!completed) {
- mResizeBitmap = null;
+ if (canvas != null) {
+ canvas.onPostDraw();
+ }
+ if (mResizeBuffer != null) {
+ mResizeBuffer.end(mAttachInfo.mHardwareCanvas);
+ if (!completed) {
+ mResizeBuffer.destroy();
+ mResizeBuffer = null;
+ }
}
}
}
@@ -1114,7 +1156,7 @@
if (mScroller != null) {
mScroller.abortAnimation();
}
- disposeResizeBitmap();
+ disposeResizeBuffer();
} else if (surfaceGenerationId != mSurface.getGenerationId() &&
mSurfaceHolder == null && mAttachInfo.mHardwareRenderer != null) {
fullRedrawNeeded = true;
@@ -1396,6 +1438,12 @@
boolean cancelDraw = attachInfo.mTreeObserver.dispatchOnPreDraw();
if (!cancelDraw && !newSurface) {
+ if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
+ for (int i = 0; i < mPendingTransitions.size(); ++i) {
+ mPendingTransitions.get(i).startChangingAnimations();
+ }
+ mPendingTransitions.clear();
+ }
mFullRedrawNeeded = false;
final long drawStartTime;
@@ -1495,15 +1543,14 @@
int mResizeAlpha;
final Paint mResizePaint = new Paint();
- public void onHardwarePreDraw(Canvas canvas) {
+ public void onHardwarePreDraw(HardwareCanvas canvas) {
canvas.translate(0, -mHardwareYOffset);
}
- public void onHardwarePostDraw(Canvas canvas) {
- if (mResizeBitmap != null) {
- canvas.translate(0, mHardwareYOffset);
+ public void onHardwarePostDraw(HardwareCanvas canvas) {
+ if (mResizeBuffer != null) {
mResizePaint.setAlpha(mResizeAlpha);
- canvas.drawBitmap(mResizeBitmap, 0, 0, mResizePaint);
+ canvas.drawHardwareLayer(mResizeBuffer, 0.0f, mHardwareYOffset, mResizePaint);
}
}
@@ -1559,15 +1606,15 @@
boolean scalingRequired = mAttachInfo.mScalingRequired;
int resizeAlpha = 0;
- if (mResizeBitmap != null) {
- long deltaTime = SystemClock.uptimeMillis() - mResizeBitmapStartTime;
- if (deltaTime < mResizeBitmapDuration) {
- float amt = deltaTime/(float)mResizeBitmapDuration;
+ if (mResizeBuffer != null) {
+ long deltaTime = SystemClock.uptimeMillis() - mResizeBufferStartTime;
+ if (deltaTime < mResizeBufferDuration) {
+ float amt = deltaTime/(float) mResizeBufferDuration;
amt = mResizeInterpolator.getInterpolation(amt);
animating = true;
resizeAlpha = 255 - (int)(amt*255);
} else {
- disposeResizeBitmap();
+ disposeResizeBuffer();
}
}
@@ -1579,7 +1626,7 @@
if (mScroller != null) {
mScroller.abortAnimation();
}
- disposeResizeBitmap();
+ disposeResizeBuffer();
}
return;
}
@@ -1863,7 +1910,7 @@
if (scrollY != mScrollY) {
if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Pan scroll changed: old="
+ mScrollY + " , new=" + scrollY);
- if (!immediate && mResizeBitmap == null) {
+ if (!immediate && mResizeBuffer == null) {
if (mScroller == null) {
mScroller = new Scroller(mView.getContext());
}
@@ -1909,20 +1956,22 @@
public void focusableViewAvailable(View v) {
checkThread();
- if (mView != null && !mView.hasFocus()) {
- v.requestFocus();
- } else {
- // the one case where will transfer focus away from the current one
- // is if the current view is a view group that prefers to give focus
- // to its children first AND the view is a descendant of it.
- mFocusedView = mView.findFocus();
- boolean descendantsHaveDibsOnFocus =
- (mFocusedView instanceof ViewGroup) &&
- (((ViewGroup) mFocusedView).getDescendantFocusability() ==
- ViewGroup.FOCUS_AFTER_DESCENDANTS);
- if (descendantsHaveDibsOnFocus && isViewDescendantOf(v, mFocusedView)) {
- // If a view gets the focus, the listener will be invoked from requestChildFocus()
+ if (mView != null) {
+ if (!mView.hasFocus()) {
v.requestFocus();
+ } else {
+ // the one case where will transfer focus away from the current one
+ // is if the current view is a view group that prefers to give focus
+ // to its children first AND the view is a descendant of it.
+ mFocusedView = mView.findFocus();
+ boolean descendantsHaveDibsOnFocus =
+ (mFocusedView instanceof ViewGroup) &&
+ (((ViewGroup) mFocusedView).getDescendantFocusability() ==
+ ViewGroup.FOCUS_AFTER_DESCENDANTS);
+ if (descendantsHaveDibsOnFocus && isViewDescendantOf(v, mFocusedView)) {
+ // If a view gets the focus, the listener will be invoked from requestChildFocus()
+ v.requestFocus();
+ }
}
}
}
@@ -1986,9 +2035,7 @@
// At this point the resources have been updated to
// have the most recent config, whatever that is. Use
// the on in them which may be newer.
- if (mView != null) {
- config = mView.getResources().getConfiguration();
- }
+ config = mView.getResources().getConfiguration();
if (force || mLastConfiguration.diff(config) != 0) {
mLastConfiguration.setTo(config);
mView.dispatchConfigurationChanged(config);
@@ -2207,6 +2254,7 @@
if ((event.getFlags()&KeyEvent.FLAG_FROM_SYSTEM) != 0) {
// The IME is trying to say this event is from the
// system! Bad bad bad!
+ //noinspection UnusedAssignment
event = KeyEvent.changeFlags(event, event.getFlags() & ~KeyEvent.FLAG_FROM_SYSTEM);
}
deliverKeyEventPostIme((KeyEvent)msg.obj, false);
@@ -2240,7 +2288,7 @@
}
}
- private void startInputEvent(InputEvent event, InputQueue.FinishedCallback finishedCallback) {
+ private void startInputEvent(InputQueue.FinishedCallback finishedCallback) {
if (mFinishedCallback != null) {
Slog.w(TAG, "Received a new input event from the input queue but there is "
+ "already an unfinished input event in progress.");
@@ -2454,9 +2502,6 @@
if (isDown) {
ensureTouchMode(true);
}
- if(false) {
- captureMotionLog("captureDispatchPointer", event);
- }
// Offset the scroll position.
if (mCurScrollY != 0) {
@@ -2534,6 +2579,7 @@
if (sendDone) {
finishInputEvent(event, handled);
}
+ //noinspection ConstantConditions
if (LOCAL_LOGV || WATCH_POINTER) {
if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
Log.i(TAG, "Done dispatching!");
@@ -2859,52 +2905,6 @@
return false;
}
- /**
- * log motion events
- */
- private static void captureMotionLog(String subTag, MotionEvent ev) {
- //check dynamic switch
- if (ev == null ||
- SystemProperties.getInt(ViewDebug.SYSTEM_PROPERTY_CAPTURE_EVENT, 0) == 0) {
- return;
- }
-
- StringBuilder sb = new StringBuilder(subTag + ": ");
- sb.append(ev.getDownTime()).append(',');
- sb.append(ev.getEventTime()).append(',');
- sb.append(ev.getAction()).append(',');
- sb.append(ev.getX()).append(',');
- sb.append(ev.getY()).append(',');
- sb.append(ev.getPressure()).append(',');
- sb.append(ev.getSize()).append(',');
- sb.append(ev.getMetaState()).append(',');
- sb.append(ev.getXPrecision()).append(',');
- sb.append(ev.getYPrecision()).append(',');
- sb.append(ev.getDeviceId()).append(',');
- sb.append(ev.getEdgeFlags());
- Log.d(TAG, sb.toString());
- }
- /**
- * log motion events
- */
- private static void captureKeyLog(String subTag, KeyEvent ev) {
- //check dynamic switch
- if (ev == null ||
- SystemProperties.getInt(ViewDebug.SYSTEM_PROPERTY_CAPTURE_EVENT, 0) == 0) {
- return;
- }
- StringBuilder sb = new StringBuilder(subTag + ": ");
- sb.append(ev.getDownTime()).append(',');
- sb.append(ev.getEventTime()).append(',');
- sb.append(ev.getAction()).append(',');
- sb.append(ev.getKeyCode()).append(',');
- sb.append(ev.getRepeatCount()).append(',');
- sb.append(ev.getMetaState()).append(',');
- sb.append(ev.getDeviceId()).append(',');
- sb.append(ev.getScanCode());
- Log.d(TAG, sb.toString());
- }
-
int enqueuePendingEvent(Object event, boolean sendDone) {
int seq = mPendingEventSeq+1;
if (seq < 0) seq = 0;
@@ -2993,10 +2993,6 @@
return;
}
- if (false) {
- captureKeyLog("captureDispatchKeyEvent", event);
- }
-
// Make sure the fallback event policy sees all keys that will be delivered to the
// view hierarchy.
mFallbackEventHandler.preDispatchKeyEvent(event);
@@ -3392,12 +3388,12 @@
private final InputHandler mInputHandler = new InputHandler() {
public void handleKey(KeyEvent event, InputQueue.FinishedCallback finishedCallback) {
- startInputEvent(event, finishedCallback);
+ startInputEvent(finishedCallback);
dispatchKey(event, true);
}
public void handleMotion(MotionEvent event, InputQueue.FinishedCallback finishedCallback) {
- startInputEvent(event, finishedCallback);
+ startInputEvent(finishedCallback);
dispatchMotion(event, true);
}
};
@@ -3429,10 +3425,6 @@
sendMessageAtTime(msg, event.getEventTime());
}
- public void dispatchMotion(MotionEvent event) {
- dispatchMotion(event, false);
- }
-
private void dispatchMotion(MotionEvent event, boolean sendDone) {
int source = event.getSource();
if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
@@ -3444,10 +3436,6 @@
}
}
- public void dispatchPointer(MotionEvent event) {
- dispatchPointer(event, false);
- }
-
private void dispatchPointer(MotionEvent event, boolean sendDone) {
Message msg = obtainMessage(DISPATCH_POINTER);
msg.obj = event;
@@ -3455,10 +3443,6 @@
sendMessageAtTime(msg, event.getEventTime());
}
- public void dispatchTrackball(MotionEvent event) {
- dispatchTrackball(event, false);
- }
-
private void dispatchTrackball(MotionEvent event, boolean sendDone) {
Message msg = obtainMessage(DISPATCH_TRACKBALL);
msg.obj = event;
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index 4aa8727..f014070 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -92,12 +92,6 @@
public static final boolean TRACE_RECYCLER = false;
/**
- * The system property of dynamic switch for capturing event information
- * when it is set, we log key events, touch/motion and trackball events
- */
- static final String SYSTEM_PROPERTY_CAPTURE_EVENT = "debug.captureevent";
-
- /**
* Profiles drawing times in the events log.
*
* @hide
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 6937573..f504b90 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -4838,6 +4838,20 @@
}
/**
+ * This method is called by LayoutTransition when there are 'changing' animations that need
+ * to start after the layout/setup phase. The request is forwarded to the ViewAncestor, who
+ * starts all pending transitions prior to the drawing phase in the current traversal.
+ *
+ * @param transition The LayoutTransition to be started on the next traversal.
+ *
+ * @hide
+ */
+ public void requestTransitionStart(LayoutTransition transition) {
+ ViewAncestor viewAncestor = getViewAncestor();
+ viewAncestor.requestTransitionStart(transition);
+ }
+
+ /**
* Return true if the pressed state should be delayed for children or descendants of this
* ViewGroup. Generally, this should be done for containers that can scroll, such as a List.
* This prevents the pressed state from appearing when the user is actually trying to scroll
diff --git a/core/java/android/view/ViewPropertyAnimator.java b/core/java/android/view/ViewPropertyAnimator.java
index 1d56e9d..9eddf23 100644
--- a/core/java/android/view/ViewPropertyAnimator.java
+++ b/core/java/android/view/ViewPropertyAnimator.java
@@ -67,6 +67,19 @@
private boolean mDurationSet = false;
/**
+ * The startDelay of the underlying Animator object. By default, we don't set the startDelay
+ * on the Animator and just use its default startDelay. If the startDelay is ever set on this
+ * Animator, then we use the startDelay that it was set to.
+ */
+ private long mStartDelay = 0;
+
+ /**
+ * A flag indicating whether the startDelay has been set on this object. If not, we don't set
+ * the startDelay on the underlying Animator, but instead just use its default startDelay.
+ */
+ private boolean mStartDelaySet = false;
+
+ /**
* The interpolator of the underlying Animator object. By default, we don't set the interpolator
* on the Animator and just use its default interpolator. If the interpolator is ever set on
* this Animator, then we use the interpolator that it was set to.
@@ -233,6 +246,60 @@
}
/**
+ * Returns the current duration of property animations. If the duration was set on this
+ * object, that value is returned. Otherwise, the default value of the underlying Animator
+ * is returned.
+ *
+ * @see #setDuration(long)
+ * @return The duration of animations, in milliseconds.
+ */
+ public long getDuration() {
+ if (mStartDelaySet) {
+ return mStartDelay;
+ } else {
+ // Just return the default from ValueAnimator, since that's what we'd get if
+ // the value has not been set otherwise
+ return new ValueAnimator().getDuration();
+ }
+ }
+
+ /**
+ * Returns the current startDelay of property animations. If the startDelay was set on this
+ * object, that value is returned. Otherwise, the default value of the underlying Animator
+ * is returned.
+ *
+ * @see #setStartDelay(long)
+ * @return The startDelay of animations, in milliseconds.
+ */
+ public long getStartDelay() {
+ if (mStartDelaySet) {
+ return mStartDelay;
+ } else {
+ // Just return the default from ValueAnimator (0), since that's what we'd get if
+ // the value has not been set otherwise
+ return 0;
+ }
+ }
+
+ /**
+ * Sets the startDelay for the underlying animator that animates the requested properties.
+ * By default, the animator uses the default value for ValueAnimator. Calling this method
+ * will cause the declared value to be used instead.
+ * @param startDelay The delay of ensuing property animations, in milliseconds. The value
+ * cannot be negative.
+ * @return This object, allowing calls to methods in this class to be chained.
+ */
+ public ViewPropertyAnimator setStartDelay(long startDelay) {
+ if (startDelay < 0) {
+ throw new IllegalArgumentException("Animators cannot have negative duration: " +
+ startDelay);
+ }
+ mStartDelaySet = true;
+ mStartDelay = startDelay;
+ return this;
+ }
+
+ /**
* Sets the interpolator for the underlying animator that animates the requested properties.
* By default, the animator uses the default interpolator for ValueAnimator. Calling this method
* will cause the declared object to be used instead.
@@ -259,6 +326,33 @@
}
/**
+ * Starts the currently pending property animations immediately. Calling <code>start()</code>
+ * is optional because all animations start automatically at the next opportunity. However,
+ * if the animations are needed to start immediately and synchronously (not at the time when
+ * the next event is processed by the hierarchy, which is when the animations would begin
+ * otherwise), then this method can be used.
+ */
+ public void start() {
+ startAnimation();
+ }
+
+ /**
+ * Cancels all property animations that are currently running or pending.
+ */
+ public void cancel() {
+ if (mAnimatorMap.size() > 0) {
+ HashMap<Animator, PropertyBundle> mAnimatorMapCopy =
+ (HashMap<Animator, PropertyBundle>)mAnimatorMap.clone();
+ Set<Animator> animatorSet = mAnimatorMapCopy.keySet();
+ for (Animator runningAnim : animatorSet) {
+ runningAnim.cancel();
+ }
+ }
+ mPendingAnimations.clear();
+ mView.getHandler().removeCallbacks(mAnimationStarter);
+ }
+
+ /**
* This method will cause the View's <code>x</code> property to be animated to the
* specified value. Animations already running on the property will be canceled.
*
@@ -598,7 +692,7 @@
// on a property will cancel a previous animation on that property, so
// there can only ever be one such animation running.
if (bundle.mPropertyMask == NONE) {
- // the animation is not longer changing anything - cancel it
+ // the animation is no longer changing anything - cancel it
animatorToCancel = runningAnim;
break;
}
diff --git a/core/java/android/webkit/JWebCoreJavaBridge.java b/core/java/android/webkit/JWebCoreJavaBridge.java
index 976e786..12391df 100644
--- a/core/java/android/webkit/JWebCoreJavaBridge.java
+++ b/core/java/android/webkit/JWebCoreJavaBridge.java
@@ -39,9 +39,6 @@
// immediately.
private boolean mHasInstantTimer;
- // Reference count the pause/resume of timers
- private int mPauseTimerRefCount;
-
private boolean mTimerPaused;
private boolean mHasDeferredTimers;
@@ -136,7 +133,7 @@
* Pause all timers.
*/
public void pause() {
- if (--mPauseTimerRefCount == 0) {
+ if (!mTimerPaused) {
mTimerPaused = true;
mHasDeferredTimers = false;
}
@@ -146,7 +143,7 @@
* Resume all timers.
*/
public void resume() {
- if (++mPauseTimerRefCount == 1) {
+ if (mTimerPaused) {
mTimerPaused = false;
if (mHasDeferredTimers) {
mHasDeferredTimers = false;
diff --git a/core/java/android/webkit/L10nUtils.java b/core/java/android/webkit/L10nUtils.java
index f59d7d0..5b4fb1d 100644
--- a/core/java/android/webkit/L10nUtils.java
+++ b/core/java/android/webkit/L10nUtils.java
@@ -69,7 +69,8 @@
com.android.internal.R.string.autofill_card_number_re, // IDS_AUTOFILL_CARD_NUMBER_RE
com.android.internal.R.string.autofill_expiration_month_re, // IDS_AUTOFILL_EXPIRATION_MONTH_RE
com.android.internal.R.string.autofill_expiration_date_re, // IDS_AUTOFILL_EXPIRATION_DATE_RE
- com.android.internal.R.string.autofill_card_ignored_re // IDS_AUTOFILL_CARD_IGNORED_RE
+ com.android.internal.R.string.autofill_card_ignored_re, // IDS_AUTOFILL_CARD_IGNORED_RE
+ com.android.internal.R.string.autofill_fax_re // IDS_AUTOFILL_FAX_RE
};
private static Context mApplicationContext;
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 8ffbda2..66fcc27 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -183,7 +183,6 @@
private boolean mJavaScriptCanOpenWindowsAutomatically = false;
private boolean mUseDoubleTree = false;
private boolean mUseWideViewport = false;
- private boolean mUseFixedViewport = false;
private boolean mSupportMultipleWindows = false;
private boolean mShrinksStandaloneImagesToFit = false;
private long mMaximumDecodedImageSize = 0; // 0 means default
@@ -361,10 +360,9 @@
}
// User agent strings.
- private static final String DESKTOP_USERAGENT =
- "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_7; en-us)"
- + " AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0"
- + " Safari/530.17";
+ private static final String DESKTOP_USERAGENT = "Mozilla/5.0 (X11; " +
+ "Linux x86_64) AppleWebKit/534.24 (KHTML, like Gecko) " +
+ "Chrome/11.0.696.34 Safari/534.24";
private static final String IPHONE_USERAGENT =
"Mozilla/5.0 (iPhone; U; CPU iPhone OS 3_0 like Mac OS X; en-us)"
+ " AppleWebKit/528.18 (KHTML, like Gecko) Version/4.0"
@@ -383,13 +381,6 @@
mDefaultTextEncoding = context.getString(com.android.internal.
R.string.default_text_encoding);
- // Detect tablet device for fixed viewport mode.
- final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
- final int landscapeWidth = Math.max(metrics.widthPixels, metrics.heightPixels);
- final int minTabletWidth = context.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.min_xlarge_screen_width);
- mUseFixedViewport = (metrics.density == 1.0f && landscapeWidth >= minTabletWidth);
-
if (sLockForLocaleSettings == null) {
sLockForLocaleSettings = new Object();
sLocale = Locale.getDefault();
@@ -1652,11 +1643,11 @@
}
/**
- * Returns whether to use fixed viewport. Fixed viewport should operate only
- * when wide viewport is on.
+ * Returns whether to use fixed viewport. Use fixed viewport
+ * whenever wide viewport is on.
*/
/* package */ boolean getUseFixedViewport() {
- return getUseWideViewPort() && mUseFixedViewport;
+ return getUseWideViewPort();
}
/**
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index e8083eb..db5ff54 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -1865,7 +1865,7 @@
Log.w(LOGTAG, "skip viewSizeChanged as w is 0");
return;
}
- int width = calculateWindowWidth(w, textwrapWidth);
+ int width = calculateWindowWidth(w);
int height = h;
if (width != w) {
float heightWidthRatio = data.mHeightWidthRatio;
@@ -1891,41 +1891,18 @@
}
// Calculate width to be used in webkit window.
- private int calculateWindowWidth(int viewWidth, int textwrapWidth) {
+ private int calculateWindowWidth(int viewWidth) {
int width = viewWidth;
if (mSettings.getUseWideViewPort()) {
if (mViewportWidth == -1) {
- if (mSettings.getLayoutAlgorithm() ==
- WebSettings.LayoutAlgorithm.NORMAL || mSettings.getUseFixedViewport()) {
- width = WebView.DEFAULT_VIEWPORT_WIDTH;
- } else {
- /*
- * if a page's minimum preferred width is wider than the
- * given "w", use it instead to get better layout result. If
- * we start a page with MAX_ZOOM_WIDTH, "w" will be always
- * wider. If we start a page with screen width, due to the
- * delay between {@link #didFirstLayout} and
- * {@link #viewSizeChanged},
- * {@link #nativeGetContentMinPrefWidth} will return a more
- * accurate value than initial 0 to result a better layout.
- * In the worse case, the native width will be adjusted when
- * next zoom or screen orientation change happens.
- */
- width = Math.min(WebView.sMaxViewportWidth, Math.max(viewWidth,
- Math.max(WebView.DEFAULT_VIEWPORT_WIDTH,
- nativeGetContentMinPrefWidth())));
- }
+ // Fixed viewport width.
+ width = WebView.DEFAULT_VIEWPORT_WIDTH;
} else if (mViewportWidth > 0) {
- if (mSettings.getUseFixedViewport()) {
- // Use website specified or desired fixed viewport width.
- width = mViewportWidth;
- } else {
- width = Math.max(viewWidth, mViewportWidth);
- }
- } else if (mSettings.getUseFixedViewport()) {
- width = mWebView.getViewWidth();
+ // Use website specified or desired fixed viewport width.
+ width = mViewportWidth;
} else {
- width = textwrapWidth;
+ // For mobile web site.
+ width = viewWidth;
}
}
return width;
@@ -2025,7 +2002,8 @@
if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "webkitDraw start");
draw.mBaseLayer = nativeRecordContent(draw.mInvalRegion, draw.mContentSize);
if (draw.mBaseLayer == 0) {
- if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "webkitDraw abort");
+ if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "webkitDraw abort, resending draw message");
+ mEventHub.sendMessage(Message.obtain(null, EventHub.WEBKIT_DRAW));
return;
}
webkitDraw(draw);
@@ -2439,8 +2417,7 @@
// in zoom overview mode.
tentativeScale = mInitialViewState.mTextWrapScale;
int tentativeViewWidth = Math.round(webViewWidth / tentativeScale);
- int windowWidth = calculateWindowWidth(tentativeViewWidth,
- tentativeViewWidth);
+ int windowWidth = calculateWindowWidth(tentativeViewWidth);
// In viewport setup time, since no content width is known, we assume
// the windowWidth will be the content width, to get a more likely
// zoom overview scale.
@@ -2449,8 +2426,7 @@
// If user choose non-overview mode.
data.mScale = Math.max(data.mScale, tentativeScale);
}
- if (mSettings.isNarrowColumnLayout() &&
- mSettings.getUseFixedViewport()) {
+ if (mSettings.isNarrowColumnLayout()) {
// In case of automatic text reflow in fixed view port mode.
mInitialViewState.mTextWrapScale =
ZoomManager.computeReadingLevelScale(data.mScale);
diff --git a/core/java/android/webkit/ZoomManager.java b/core/java/android/webkit/ZoomManager.java
index f2a1ec3..e330737 100644
--- a/core/java/android/webkit/ZoomManager.java
+++ b/core/java/android/webkit/ZoomManager.java
@@ -1019,19 +1019,11 @@
WebSettings settings = mWebView.getSettings();
int newZoomOverviewWidth = mZoomOverviewWidth;
if (settings.getUseWideViewPort()) {
- if (!settings.getUseFixedViewport()) {
- // limit mZoomOverviewWidth upper bound to
- // sMaxViewportWidth so that if the page doesn't behave
- // well, the WebView won't go insane. limit the lower
- // bound to match the default scale for mobile sites.
- newZoomOverviewWidth = Math.min(WebView.sMaxViewportWidth,
- Math.max((int) (viewWidth * mInvDefaultScale),
- Math.max(drawData.mMinPrefWidth, drawData.mViewSize.x)));
- } else if (drawData.mContentSize.x > 0) {
+ if (drawData.mContentSize.x > 0) {
// The webkitDraw for layers will not populate contentSize, and it'll be
// ignored for zoom overview width update.
- final int contentWidth = Math.max(drawData.mContentSize.x, drawData.mMinPrefWidth);
- newZoomOverviewWidth = Math.min(WebView.sMaxViewportWidth, contentWidth);
+ newZoomOverviewWidth = Math.min(WebView.sMaxViewportWidth,
+ drawData.mContentSize.x);
}
} else {
// If not use wide viewport, use view width as the zoom overview width.
diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java
index bac849e..6f30452 100644
--- a/core/java/android/widget/LinearLayout.java
+++ b/core/java/android/widget/LinearLayout.java
@@ -696,7 +696,8 @@
mTotalLength += mDividerHeight;
}
- if (useLargestChild && heightMode == MeasureSpec.AT_MOST) {
+ if (useLargestChild &&
+ (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) {
mTotalLength = 0;
for (int i = 0; i < count; ++i) {
@@ -809,6 +810,31 @@
} else {
alternativeMaxWidth = Math.max(alternativeMaxWidth,
weightedMaxWidth);
+
+
+ // We have no limit, so make all weighted views as tall as the largest child.
+ // Children will have already been measured once.
+ if (useLargestChild && widthMode == MeasureSpec.UNSPECIFIED) {
+ for (int i = 0; i < count; i++) {
+ final View child = getVirtualChildAt(i);
+
+ if (child == null || child.getVisibility() == View.GONE) {
+ continue;
+ }
+
+ final LinearLayout.LayoutParams lp =
+ (LinearLayout.LayoutParams) child.getLayoutParams();
+
+ float childExtra = lp.weight;
+ if (childExtra > 0) {
+ child.measure(
+ MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(),
+ MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(largestChildHeight,
+ MeasureSpec.EXACTLY));
+ }
+ }
+ }
}
if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {
@@ -1044,7 +1070,8 @@
maxHeight = Math.max(maxHeight, ascent + descent);
}
- if (useLargestChild && widthMode == MeasureSpec.AT_MOST) {
+ if (useLargestChild &&
+ (widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED)) {
mTotalLength = 0;
for (int i = 0; i < count; ++i) {
@@ -1200,6 +1227,29 @@
}
} else {
alternativeMaxHeight = Math.max(alternativeMaxHeight, weightedMaxHeight);
+
+ // We have no limit, so make all weighted views as wide as the largest child.
+ // Children will have already been measured once.
+ if (useLargestChild && widthMode == MeasureSpec.UNSPECIFIED) {
+ for (int i = 0; i < count; i++) {
+ final View child = getVirtualChildAt(i);
+
+ if (child == null || child.getVisibility() == View.GONE) {
+ continue;
+ }
+
+ final LinearLayout.LayoutParams lp =
+ (LinearLayout.LayoutParams) child.getLayoutParams();
+
+ float childExtra = lp.weight;
+ if (childExtra > 0) {
+ child.measure(
+ MeasureSpec.makeMeasureSpec(largestChildWidth, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(child.getMeasuredHeight(),
+ MeasureSpec.EXACTLY));
+ }
+ }
+ }
}
if (!allFillParent && heightMode != MeasureSpec.EXACTLY) {
diff --git a/core/java/android/widget/StackView.java b/core/java/android/widget/StackView.java
index 71c91e1..c4ba7c8 100644
--- a/core/java/android/widget/StackView.java
+++ b/core/java/android/widget/StackView.java
@@ -116,7 +116,7 @@
private static final int MIN_TIME_BETWEEN_INTERACTION_AND_AUTOADVANCE = 5000;
- private static long MIN_TIME_BETWEEN_SCROLLS = 100;
+ private static final long MIN_TIME_BETWEEN_SCROLLS = 100;
/**
* These variables are all related to the current state of touch interaction
@@ -213,8 +213,7 @@
* Animate the views between different relative indexes within the {@link AdapterViewAnimator}
*/
void transformViewForTransition(int fromIndex, int toIndex, final View view, boolean animate) {
- ObjectAnimator alphaOa = null;
- ObjectAnimator oldAlphaOa = null;
+ ObjectAnimator alphaOa;
if (!animate) {
((StackFrame) view).cancelSliderAnimator();
@@ -1276,13 +1275,11 @@
boolean firstPass = true;
parentRect.set(0, 0, 0, 0);
- int depth = 0;
while (p.getParent() != null && p.getParent() instanceof View
&& !parentRect.contains(globalInvalidateRect)) {
if (!firstPass) {
globalInvalidateRect.offset(p.getLeft() - p.getScrollX(), p.getTop()
- p.getScrollY());
- depth++;
}
firstPass = false;
p = (View) p.getParent();
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index d58c72b..3875765 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -16,11 +16,6 @@
package android.widget;
-import com.android.internal.util.FastMath;
-import com.android.internal.widget.EditableInputConnection;
-
-import org.xmlpull.v1.XmlPullParserException;
-
import android.R;
import android.content.ClipData;
import android.content.ClipData.Item;
@@ -129,6 +124,11 @@
import android.view.inputmethod.InputMethodManager;
import android.widget.RemoteViews.RemoteView;
+import com.android.internal.util.FastMath;
+import com.android.internal.widget.EditableInputConnection;
+
+import org.xmlpull.v1.XmlPullParserException;
+
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.text.BreakIterator;
@@ -320,6 +320,7 @@
private int mTextEditSuggestionItemLayout;
private SuggestionsPopupWindow mSuggestionsPopupWindow;
private SuggestionRangeSpan mSuggestionRangeSpan;
+ private boolean mSuggestionsEnabled = true;
private int mCursorDrawableRes;
private final Drawable[] mCursorDrawable = new Drawable[2];
@@ -329,7 +330,7 @@
private Drawable mSelectHandleRight;
private Drawable mSelectHandleCenter;
- private int mLastDownPositionX, mLastDownPositionY;
+ private float mLastDownPositionX, mLastDownPositionY;
private Callback mCustomSelectionActionModeCallback;
private final int mSquaredTouchSlopDistance;
@@ -806,6 +807,10 @@
case com.android.internal.R.styleable.TextView_textIsSelectable:
mTextIsSelectable = a.getBoolean(attr, false);
break;
+
+ case com.android.internal.R.styleable.TextView_suggestionsEnabled:
+ mSuggestionsEnabled = a.getBoolean(attr, true);
+ break;
}
}
a.recycle();
@@ -2898,8 +2903,7 @@
setText(mCharWrapper, mBufferType, false, oldlen);
}
- private static class CharWrapper
- implements CharSequence, GetChars, GraphicsOperations {
+ private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
private char[] mChars;
private int mStart, mLength;
@@ -7323,8 +7327,8 @@
}
if (action == MotionEvent.ACTION_DOWN) {
- mLastDownPositionX = (int) event.getX();
- mLastDownPositionY = (int) event.getY();
+ mLastDownPositionX = event.getX();
+ mLastDownPositionY = event.getY();
// Reset this state; it will be re-set if super.onTouchEvent
// causes focus to move to the view.
@@ -7758,16 +7762,6 @@
hasPrimaryClip());
}
- private boolean isWordCharacter(int c, int type) {
- return (c == '\'' || c == '"' ||
- type == Character.UPPERCASE_LETTER ||
- type == Character.LOWERCASE_LETTER ||
- type == Character.TITLECASE_LETTER ||
- type == Character.MODIFIER_LETTER ||
- type == Character.OTHER_LETTER || // Should handle asian characters
- type == Character.DECIMAL_DIGIT_NUMBER);
- }
-
private static long packRangeInLong(int start, int end) {
return (((long) start) << 32) | end;
}
@@ -8140,7 +8134,7 @@
// Long press in empty space moves cursor and shows the Paste affordance if available.
if (!isPositionOnText(mLastDownPositionX, mLastDownPositionY) &&
mInsertionControllerEnabled) {
- final int offset = getOffset(mLastDownPositionX, mLastDownPositionY);
+ final int offset = getOffsetForPosition(mLastDownPositionX, mLastDownPositionY);
stopSelectionActionMode();
Selection.setSelection((Spannable) mText, offset);
getInsertionController().showWithPaste();
@@ -8208,7 +8202,7 @@
private final ViewGroup[] mSuggestionViews = new ViewGroup[2];
private final int[] mSuggestionViewLayouts = new int[] {
mTextEditSuggestionsBottomWindowLayout, mTextEditSuggestionsTopWindowLayout};
- private WordIterator mWordIterator;
+ private WordIterator mSuggestionWordIterator;
private TextAppearanceSpan[] mHighlightSpans = new TextAppearanceSpan[0];
public SuggestionsPopupWindow() {
@@ -8344,26 +8338,27 @@
}
private long[] getWordLimits(CharSequence text) {
- if (mWordIterator == null) mWordIterator = new WordIterator(); // TODO locale
- mWordIterator.setCharSequence(text);
+ // TODO locale for mSuggestionWordIterator
+ if (mSuggestionWordIterator == null) mSuggestionWordIterator = new WordIterator();
+ mSuggestionWordIterator.setCharSequence(text);
// First pass will simply count the number of words to be able to create an array
// Not too expensive since previous break positions are cached by the BreakIterator
int nbWords = 0;
- int position = mWordIterator.following(0);
+ int position = mSuggestionWordIterator.following(0);
while (position != BreakIterator.DONE) {
nbWords++;
- position = mWordIterator.following(position);
+ position = mSuggestionWordIterator.following(position);
}
int index = 0;
long[] result = new long[nbWords];
- position = mWordIterator.following(0);
+ position = mSuggestionWordIterator.following(0);
while (position != BreakIterator.DONE) {
- int wordStart = mWordIterator.getBeginning(position);
+ int wordStart = mSuggestionWordIterator.getBeginning(position);
result[index++] = packRangeInLong(wordStart, position);
- position = mWordIterator.following(position);
+ position = mSuggestionWordIterator.following(position);
}
return result;
@@ -8601,6 +8596,8 @@
}
void showSuggestions() {
+ if (!mSuggestionsEnabled || !isTextEditable()) return;
+
if (mSuggestionsPopupWindow == null) {
mSuggestionsPopupWindow = new SuggestionsPopupWindow();
}
@@ -8615,6 +8612,31 @@
}
/**
+ * Some parts of the text can have alternate suggestion text attached. This is typically done by
+ * the IME by adding {@link SuggestionSpan}s to the text.
+ *
+ * When suggestions are enabled (default), this list of suggestions will be displayed when the
+ * user double taps on these parts of the text. No suggestions are displayed when this value is
+ * false. Use {@link #setSuggestionsEnabled(boolean)} to change this value.
+ *
+ * @return true if the suggestions popup window is enabled.
+ *
+ * @attr ref android.R.styleable#TextView_suggestionsEnabled
+ */
+ public boolean isSuggestionsEnabled() {
+ return mSuggestionsEnabled;
+ }
+
+ /**
+ * Enables or disables the suggestion popup. See {@link #isSuggestionsEnabled()}.
+ *
+ * @param enabled Whether or not suggestions are enabled.
+ */
+ public void setSuggestionsEnabled(boolean enabled) {
+ mSuggestionsEnabled = enabled;
+ }
+
+ /**
* If provided, this ActionMode.Callback will be used to create the ActionMode when text
* selection is initiated in this View.
*
@@ -9115,7 +9137,7 @@
public abstract void updateOffset(int offset);
- public abstract void updatePosition(int x, int y);
+ public abstract void updatePosition(float x, float y);
protected void positionAtCursorOffset(int offset) {
addPositionToTouchUpFilter(offset);
@@ -9215,7 +9237,7 @@
final float newPosX = rawX - mTouchToWindowOffsetX + mHotspotX;
final float newPosY = rawY - mTouchToWindowOffsetY + mTouchOffsetY;
- updatePosition(Math.round(newPosX), Math.round(newPosY));
+ updatePosition(newPosX, newPosY);
break;
}
@@ -9366,8 +9388,8 @@
}
@Override
- public void updatePosition(int x, int y) {
- updateOffset(getOffset(x, y));
+ public void updatePosition(float x, float y) {
+ updateOffset(getOffsetForPosition(x, y));
}
void showPastePopupWindow() {
@@ -9421,11 +9443,11 @@
}
@Override
- public void updatePosition(int x, int y) {
+ public void updatePosition(float x, float y) {
final int selectionStart = getSelectionStart();
final int selectionEnd = getSelectionEnd();
- int offset = getOffset(x, y);
+ int offset = getOffsetForPosition(x, y);
// No need to redraw when the offset is unchanged
if (offset == selectionStart) return;
@@ -9458,11 +9480,11 @@
}
@Override
- public void updatePosition(int x, int y) {
+ public void updatePosition(float x, float y) {
final int selectionStart = getSelectionStart();
final int selectionEnd = getSelectionEnd();
- int offset = getOffset(x, y);
+ int offset = getOffsetForPosition(x, y);
// No need to redraw when the offset is unchanged
if (offset == selectionEnd) return;
@@ -9560,7 +9582,7 @@
// Double tap detection
private long mPreviousTapUpTime = 0;
- private int mPreviousTapPositionX, mPreviousTapPositionY;
+ private float mPreviousTapPositionX, mPreviousTapPositionY;
SelectionModifierCursorController() {
resetTouchOffsets();
@@ -9593,19 +9615,19 @@
if (isTextEditable() || mTextIsSelectable) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
- final int x = (int) event.getX();
- final int y = (int) event.getY();
+ final float x = event.getX();
+ final float y = event.getY();
// Remember finger down position, to be able to start selection from there
- mMinTouchOffset = mMaxTouchOffset = getOffset(x, y);
+ mMinTouchOffset = mMaxTouchOffset = getOffsetForPosition(x, y);
// Double tap detection
long duration = SystemClock.uptimeMillis() - mPreviousTapUpTime;
if (duration <= ViewConfiguration.getDoubleTapTimeout() &&
isPositionOnText(x, y)) {
- final int deltaX = x - mPreviousTapPositionX;
- final int deltaY = y - mPreviousTapPositionY;
- final int distanceSquared = deltaX * deltaX + deltaY * deltaY;
+ final float deltaX = x - mPreviousTapPositionX;
+ final float deltaY = y - mPreviousTapPositionY;
+ final float distanceSquared = deltaX * deltaX + deltaY * deltaY;
if (distanceSquared < mSquaredTouchSlopDistance) {
showSuggestions();
mDiscardNextActionUp = true;
@@ -9641,9 +9663,7 @@
private void updateMinAndMaxOffsets(MotionEvent event) {
int pointerCount = event.getPointerCount();
for (int index = 0; index < pointerCount; index++) {
- final int x = (int) event.getX(index);
- final int y = (int) event.getY(index);
- int offset = getOffset(x, y);
+ int offset = getOffsetForPosition(event.getX(index), event.getY(index));
if (offset < mMinTouchOffset) mMinTouchOffset = offset;
if (offset > mMaxTouchOffset) mMaxTouchOffset = offset;
}
@@ -9701,41 +9721,40 @@
}
/**
- * Get the offset character closest to the specified absolute position.
+ * Get the character offset closest to the specified absolute position. A typical use case is to
+ * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
*
* @param x The horizontal absolute position of a point on screen
* @param y The vertical absolute position of a point on screen
* @return the character offset for the character whose position is closest to the specified
* position. Returns -1 if there is no layout.
- *
- * @hide
*/
- public int getOffset(int x, int y) {
+ public int getOffsetForPosition(float x, float y) {
if (getLayout() == null) return -1;
final int line = getLineAtCoordinate(y);
final int offset = getOffsetAtCoordinate(line, x);
return offset;
}
- private int convertToLocalHorizontalCoordinate(int x) {
+ private float convertToLocalHorizontalCoordinate(float x) {
x -= getTotalPaddingLeft();
// Clamp the position to inside of the view.
- x = Math.max(0, x);
+ x = Math.max(0.0f, x);
x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
x += getScrollX();
return x;
}
- private int getLineAtCoordinate(int y) {
+ private int getLineAtCoordinate(float y) {
y -= getTotalPaddingTop();
// Clamp the position to inside of the view.
- y = Math.max(0, y);
+ y = Math.max(0.0f, y);
y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
y += getScrollY();
- return getLayout().getLineForVertical(y);
+ return getLayout().getLineForVertical((int) y);
}
- private int getOffsetAtCoordinate(int line, int x) {
+ private int getOffsetAtCoordinate(int line, float x) {
x = convertToLocalHorizontalCoordinate(x);
return getLayout().getOffsetForHorizontal(line, x);
}
@@ -9743,7 +9762,7 @@
/** Returns true if the screen coordinates position (x,y) corresponds to a character displayed
* in the view. Returns false when the position is in the empty space of left/right of text.
*/
- private boolean isPositionOnText(int x, int y) {
+ private boolean isPositionOnText(float x, float y) {
if (getLayout() == null) return false;
final int line = getLineAtCoordinate(y);
@@ -9765,7 +9784,7 @@
return true;
case DragEvent.ACTION_DRAG_LOCATION:
- final int offset = getOffset((int) event.getX(), (int) event.getY());
+ final int offset = getOffsetForPosition(event.getX(), event.getY());
Selection.setSelection((Spannable)mText, offset);
return true;
@@ -9789,7 +9808,7 @@
content.append(item.coerceToText(TextView.this.mContext));
}
- final int offset = getOffset((int) event.getX(), (int) event.getY());
+ final int offset = getOffsetForPosition(event.getX(), event.getY());
Object localState = event.getLocalState();
DragLocalState dragLocalState = null;
diff --git a/core/java/com/android/internal/app/ActionBarImpl.java b/core/java/com/android/internal/app/ActionBarImpl.java
index 1e576ce..183cfbd 100644
--- a/core/java/com/android/internal/app/ActionBarImpl.java
+++ b/core/java/com/android/internal/app/ActionBarImpl.java
@@ -16,25 +16,27 @@
package com.android.internal.app;
+import com.android.internal.R;
import com.android.internal.view.menu.MenuBuilder;
import com.android.internal.view.menu.MenuPopupHelper;
import com.android.internal.view.menu.SubMenuBuilder;
-import com.android.internal.widget.AbsActionBarView;
import com.android.internal.widget.ActionBarContainer;
import com.android.internal.widget.ActionBarContextView;
import com.android.internal.widget.ActionBarView;
+import com.android.internal.widget.ScrollingTabContainerView;
import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
-import android.animation.TimeInterpolator;
import android.app.ActionBar;
import android.app.Activity;
import android.app.Dialog;
import android.app.FragmentTransaction;
import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.view.ActionMode;
@@ -43,10 +45,7 @@
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
-import android.view.ViewGroup;
import android.view.Window;
-import android.view.animation.DecelerateInterpolator;
-import android.widget.HorizontalScrollView;
import android.widget.SpinnerAdapter;
import java.lang.ref.WeakReference;
@@ -71,7 +70,7 @@
private ActionBarContextView mContextView;
private ActionBarContainer mSplitView;
private View mContentView;
- private ViewGroup mExternalTabView;
+ private ScrollingTabContainerView mTabScrollView;
private ArrayList<TabImpl> mTabs = new ArrayList<TabImpl>();
@@ -90,16 +89,17 @@
private static final int INVALID_POSITION = -1;
private int mContextDisplayMode;
+ private boolean mHasEmbeddedTabs;
+ private int mContentHeight;
final Handler mHandler = new Handler();
+ Runnable mTabSelector;
private Animator mCurrentShowAnim;
private Animator mCurrentModeAnim;
private boolean mShowHideAnimationEnabled;
boolean mWasHiddenBeforeMode;
- private static final TimeInterpolator sFadeOutInterpolator = new DecelerateInterpolator();
-
final AnimatorListener mHideListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -150,21 +150,59 @@
"with a compatible window decor layout");
}
+ mHasEmbeddedTabs = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.action_bar_embed_tabs);
mActionView.setContextView(mContextView);
mContextDisplayMode = mActionView.isSplitActionBar() ?
CONTEXT_DISPLAY_SPLIT : CONTEXT_DISPLAY_NORMAL;
- if (!mActionView.hasEmbeddedTabs()) {
- HorizontalScrollView tabScroller = new HorizontalScrollView(mContext);
- ViewGroup tabContainer = mActionView.createTabContainer();
- tabScroller.setHorizontalFadingEdgeEnabled(true);
- tabScroller.addView(tabContainer);
+ TypedArray a = mContext.obtainStyledAttributes(null, R.styleable.ActionBar);
+ mContentHeight = a.getLayoutDimension(R.styleable.ActionBar_height, 0);
+ a.recycle();
+ }
+
+ public void onConfigurationChanged(Configuration newConfig) {
+ mHasEmbeddedTabs = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.action_bar_embed_tabs);
+
+ // Switch tab layout configuration if needed
+ if (!mHasEmbeddedTabs) {
+ mActionView.setEmbeddedTabView(null);
+ mContainerView.setTabContainer(mTabScrollView);
+ } else {
+ mContainerView.setTabContainer(null);
+ if (mTabScrollView != null) {
+ mTabScrollView.setVisibility(View.VISIBLE);
+ }
+ mActionView.setEmbeddedTabView(mTabScrollView);
+ }
+
+ TypedArray a = mContext.obtainStyledAttributes(null, R.styleable.ActionBar);
+ mContentHeight = a.getLayoutDimension(R.styleable.ActionBar_height, 0);
+ a.recycle();
+
+ if (mTabScrollView != null) {
+ mTabScrollView.getLayoutParams().height = mContentHeight;
+ mTabScrollView.requestLayout();
+ }
+ }
+
+ private void ensureTabsExist() {
+ if (mTabScrollView != null) {
+ return;
+ }
+
+ ScrollingTabContainerView tabScroller = mActionView.createTabContainer();
+
+ if (mHasEmbeddedTabs) {
+ tabScroller.setVisibility(View.VISIBLE);
+ mActionView.setEmbeddedTabView(tabScroller);
+ } else {
tabScroller.setVisibility(getNavigationMode() == NAVIGATION_MODE_TABS ?
View.VISIBLE : View.GONE);
- mActionView.setExternalTabLayout(tabContainer);
mContainerView.setTabContainer(tabScroller);
- mExternalTabView = tabScroller;
}
+ mTabScrollView = tabScroller;
}
/**
@@ -269,7 +307,7 @@
selectTab(null);
}
mTabs.clear();
- mActionView.removeAllTabs();
+ mTabScrollView.removeAllTabs();
mSavedTabPosition = INVALID_POSITION;
}
@@ -365,7 +403,8 @@
@Override
public void addTab(Tab tab, boolean setSelected) {
- mActionView.addTab(tab, setSelected);
+ ensureTabsExist();
+ mTabScrollView.addTab(tab, setSelected);
configureTab(tab, mTabs.size());
if (setSelected) {
selectTab(tab);
@@ -374,7 +413,8 @@
@Override
public void addTab(Tab tab, int position, boolean setSelected) {
- mActionView.addTab(tab, position, setSelected);
+ ensureTabsExist();
+ mTabScrollView.addTab(tab, position, setSelected);
configureTab(tab, position);
if (setSelected) {
selectTab(tab);
@@ -393,9 +433,14 @@
@Override
public void removeTabAt(int position) {
+ if (mTabScrollView == null) {
+ // No tabs around to remove
+ return;
+ }
+
int selectedTabPosition = mSelectedTab != null
? mSelectedTab.getPosition() : mSavedTabPosition;
- mActionView.removeTabAt(position);
+ mTabScrollView.removeTabAt(position);
TabImpl removedTab = mTabs.remove(position);
if (removedTab != null) {
removedTab.setPosition(-1);
@@ -424,9 +469,10 @@
if (mSelectedTab == tab) {
if (mSelectedTab != null) {
mSelectedTab.getCallback().onTabReselected(mSelectedTab, trans);
+ mTabScrollView.animateToTab(tab.getPosition());
}
} else {
- mActionView.setTabSelected(tab != null ? tab.getPosition() : Tab.INVALID_POSITION);
+ mTabScrollView.setTabSelected(tab != null ? tab.getPosition() : Tab.INVALID_POSITION);
if (mSelectedTab != null) {
mSelectedTab.getCallback().onTabUnselected(mSelectedTab, trans);
}
@@ -705,7 +751,9 @@
@Override
public Tab setCustomView(View view) {
mCustomView = view;
- if (mPosition >= 0) mActionView.updateTab(mPosition);
+ if (mPosition >= 0) {
+ mTabScrollView.updateTab(mPosition);
+ }
return this;
}
@@ -736,7 +784,9 @@
@Override
public Tab setIcon(Drawable icon) {
mIcon = icon;
- if (mPosition >= 0) mActionView.updateTab(mPosition);
+ if (mPosition >= 0) {
+ mTabScrollView.updateTab(mPosition);
+ }
return this;
}
@@ -748,7 +798,9 @@
@Override
public Tab setText(CharSequence text) {
mText = text;
- if (mPosition >= 0) mActionView.updateTab(mPosition);
+ if (mPosition >= 0) {
+ mTabScrollView.updateTab(mPosition);
+ }
return this;
}
@@ -818,15 +870,16 @@
mSavedTabPosition = getSelectedNavigationIndex();
selectTab(null);
if (!mActionView.hasEmbeddedTabs()) {
- mExternalTabView.setVisibility(View.GONE);
+ mTabScrollView.setVisibility(View.GONE);
}
break;
}
mActionView.setNavigationMode(mode);
switch (mode) {
case NAVIGATION_MODE_TABS:
+ ensureTabsExist();
if (!mActionView.hasEmbeddedTabs()) {
- mExternalTabView.setVisibility(View.VISIBLE);
+ mTabScrollView.setVisibility(View.VISIBLE);
}
if (mSavedTabPosition != INVALID_POSITION) {
setSelectedNavigationItem(mSavedTabPosition);
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index 0f086f6..5e9cd23 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -32,7 +32,6 @@
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
-import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.LogManager;
import java.util.TimeZone;
@@ -45,6 +44,7 @@
*/
public class RuntimeInit {
private final static String TAG = "AndroidRuntime";
+ private final static boolean DEBUG = false;
/** true if commonInit() has been called */
private static boolean initialized;
@@ -89,14 +89,14 @@
}
private static final void commonInit() {
- if (false) Slog.d(TAG, "Entered RuntimeInit!");
+ if (DEBUG) Slog.d(TAG, "Entered RuntimeInit!");
/* set default handler; this applies to all threads in the VM */
Thread.setDefaultUncaughtExceptionHandler(new UncaughtHandler());
int hasQwerty = getQwertyKeyboard();
- if (false) Slog.d(TAG, ">>>>> qwerty keyboard = " + hasQwerty);
+ if (DEBUG) Slog.d(TAG, ">>>>> qwerty keyboard = " + hasQwerty);
if (hasQwerty == 1) {
System.setProperty("qwerty", "1");
}
@@ -183,11 +183,6 @@
*/
private static void invokeStaticMain(String className, String[] argv)
throws ZygoteInit.MethodAndArgsCaller {
-
- // We want to be fairly aggressive about heap utilization, to avoid
- // holding on to a lot of memory that isn't needed.
- VMRuntime.getRuntime().setTargetHeapUtilization(0.75f);
-
Class<?> cl;
try {
@@ -225,6 +220,13 @@
}
public static final void main(String[] argv) {
+ if (argv.length == 2 && argv[1].equals("application")) {
+ if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting application");
+ redirectLogStreams();
+ } else {
+ if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting tool");
+ }
+
commonInit();
/*
@@ -233,7 +235,7 @@
*/
finishInit();
- if (false) Slog.d(TAG, "Leaving RuntimeInit!");
+ if (DEBUG) Slog.d(TAG, "Leaving RuntimeInit!");
}
public static final native void finishInit();
@@ -245,7 +247,6 @@
*
* Current recognized args:
* <ul>
- * <li> --nice-name=<i>nice name to appear in ps</i>
* <li> <code> [--] <start class name> <args>
* </ul>
*
@@ -253,45 +254,60 @@
*/
public static final void zygoteInit(String[] argv)
throws ZygoteInit.MethodAndArgsCaller {
- // TODO: Doing this here works, but it seems kind of arbitrary. Find
- // a better place. The goal is to set it up for applications, but not
- // tools like am.
- System.out.close();
- System.setOut(new AndroidPrintStream(Log.INFO, "System.out"));
- System.err.close();
- System.setErr(new AndroidPrintStream(Log.WARN, "System.err"));
+ if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting application from zygote");
+
+ redirectLogStreams();
commonInit();
zygoteInitNative();
- int curArg = 0;
- for ( /* curArg */ ; curArg < argv.length; curArg++) {
- String arg = argv[curArg];
+ applicationInit(argv);
+ }
- if (arg.equals("--")) {
- curArg++;
- break;
- } else if (!arg.startsWith("--")) {
- break;
- } else if (arg.startsWith("--nice-name=")) {
- String niceName = arg.substring(arg.indexOf('=') + 1);
- Process.setArgV0(niceName);
- }
- }
+ /**
+ * The main function called when an application is started through a
+ * wrapper process.
+ *
+ * When the wrapper starts, the runtime starts {@link RuntimeInit#main}
+ * which calls {@link WrapperInit#main} which then calls this method.
+ * So we don't need to call commonInit() here.
+ *
+ * @param argv arg strings
+ */
+ public static void wrapperInit(String[] argv)
+ throws ZygoteInit.MethodAndArgsCaller {
+ if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting application from wrapper");
- if (curArg == argv.length) {
- Slog.e(TAG, "Missing classname argument to RuntimeInit!");
+ applicationInit(argv);
+ }
+
+ private static void applicationInit(String[] argv)
+ throws ZygoteInit.MethodAndArgsCaller {
+ // We want to be fairly aggressive about heap utilization, to avoid
+ // holding on to a lot of memory that isn't needed.
+ VMRuntime.getRuntime().setTargetHeapUtilization(0.75f);
+
+ final Arguments args;
+ try {
+ args = new Arguments(argv);
+ } catch (IllegalArgumentException ex) {
+ Slog.e(TAG, ex.getMessage());
// let the process exit
return;
}
// Remaining arguments are passed to the start class's static main
+ invokeStaticMain(args.startClass, args.startArgs);
+ }
- String startClass = argv[curArg++];
- String[] startArgs = new String[argv.length - curArg];
-
- System.arraycopy(argv, curArg, startArgs, 0, startArgs.length);
- invokeStaticMain(startClass, startArgs);
+ /**
+ * Redirect System.out and System.err to the Android log.
+ */
+ public static void redirectLogStreams() {
+ System.out.close();
+ System.setOut(new AndroidPrintStream(Log.INFO, "System.out"));
+ System.err.close();
+ System.setErr(new AndroidPrintStream(Log.WARN, "System.err"));
}
public static final native void zygoteInitNative();
@@ -351,4 +367,55 @@
// Register handlers for DDM messages.
android.ddm.DdmRegister.registerHandlers();
}
+
+ /**
+ * Handles argument parsing for args related to the runtime.
+ *
+ * Current recognized args:
+ * <ul>
+ * <li> <code> [--] <start class name> <args>
+ * </ul>
+ */
+ static class Arguments {
+ /** first non-option argument */
+ String startClass;
+
+ /** all following arguments */
+ String[] startArgs;
+
+ /**
+ * Constructs instance and parses args
+ * @param args runtime command-line args
+ * @throws IllegalArgumentException
+ */
+ Arguments(String args[]) throws IllegalArgumentException {
+ parseArgs(args);
+ }
+
+ /**
+ * Parses the commandline arguments intended for the Runtime.
+ */
+ private void parseArgs(String args[])
+ throws IllegalArgumentException {
+ int curArg = 0;
+ for (; curArg < args.length; curArg++) {
+ String arg = args[curArg];
+
+ if (arg.equals("--")) {
+ curArg++;
+ break;
+ } else if (!arg.startsWith("--")) {
+ break;
+ }
+ }
+
+ if (curArg == args.length) {
+ throw new IllegalArgumentException("Missing classname argument to RuntimeInit!");
+ }
+
+ startClass = args[curArg++];
+ startArgs = new String[args.length - curArg];
+ System.arraycopy(args, curArg, startArgs, 0, startArgs.length);
+ }
+ }
}
diff --git a/core/java/com/android/internal/os/WrapperInit.java b/core/java/com/android/internal/os/WrapperInit.java
new file mode 100644
index 0000000..18d6caa
--- /dev/null
+++ b/core/java/com/android/internal/os/WrapperInit.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import android.os.Process;
+import android.util.Slog;
+
+import java.io.DataOutputStream;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import libcore.io.IoUtils;
+import libcore.io.Libcore;
+
+import dalvik.system.Zygote;
+
+/**
+ * Startup class for the wrapper process.
+ * @hide
+ */
+public class WrapperInit {
+ private final static String TAG = "AndroidRuntime";
+
+ /**
+ * Class not instantiable.
+ */
+ private WrapperInit() {
+ }
+
+ /**
+ * The main function called when starting a runtime application through a
+ * wrapper process instead of by forking Zygote.
+ *
+ * The first argument specifies the file descriptor for a pipe that should receive
+ * the pid of this process, or 0 if none. The remaining arguments are passed to
+ * the runtime.
+ *
+ * @param args The command-line arguments.
+ */
+ public static void main(String[] args) {
+ try {
+ int fdNum = Integer.parseInt(args[0], 10);
+ if (fdNum != 0) {
+ try {
+ FileDescriptor fd = ZygoteInit.createFileDescriptor(fdNum);
+ DataOutputStream os = new DataOutputStream(new FileOutputStream(fd));
+ os.writeInt(Process.myPid());
+ os.close();
+ IoUtils.closeQuietly(fd);
+ } catch (IOException ex) {
+ Slog.d(TAG, "Could not write pid of wrapped process to Zygote pipe.", ex);
+ }
+ }
+
+ String[] runtimeArgs = new String[args.length - 1];
+ System.arraycopy(args, 1, runtimeArgs, 0, runtimeArgs.length);
+ RuntimeInit.wrapperInit(runtimeArgs);
+ } catch (ZygoteInit.MethodAndArgsCaller caller) {
+ caller.run();
+ }
+ }
+
+ /**
+ * Executes a runtime application with a wrapper command.
+ * This method never returns.
+ *
+ * @param invokeWith The wrapper command.
+ * @param niceName The nice name for the application, or null if none.
+ * @param pipeFd The pipe to which the application's pid should be written, or null if none.
+ * @param args Arguments for {@link RuntimeInit.main}.
+ */
+ public static void execApplication(String invokeWith, String niceName,
+ FileDescriptor pipeFd, String[] args) {
+ StringBuilder command = new StringBuilder(invokeWith);
+ command.append(" /system/bin/app_process /system/bin --application");
+ if (niceName != null) {
+ command.append(" '--nice-name=").append(niceName).append("'");
+ }
+ command.append(" com.android.internal.os.WrapperInit ");
+ command.append(pipeFd != null ? pipeFd.getInt$() : 0);
+ Zygote.appendQuotedShellArgs(command, args);
+ Zygote.execShell(command.toString());
+ }
+
+ /**
+ * Executes a standalone application with a wrapper command.
+ * This method never returns.
+ *
+ * @param invokeWith The wrapper command.
+ * @param classPath The class path.
+ * @param className The class name to invoke.
+ * @param args Arguments for the main() method of the specified class.
+ */
+ public static void execStandalone(String invokeWith, String classPath, String className,
+ String[] args) {
+ StringBuilder command = new StringBuilder(invokeWith);
+ command.append(" /system/bin/dalvikvm -classpath '").append(classPath);
+ command.append("' ").append(className);
+ Zygote.appendQuotedShellArgs(command, args);
+ Zygote.execShell(command.toString());
+ }
+}
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index c473fd2..b872e22 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -26,14 +26,20 @@
import dalvik.system.Zygote;
import java.io.BufferedReader;
+import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileDescriptor;
+import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.ArrayList;
+import libcore.io.ErrnoException;
+import libcore.io.IoUtils;
+import libcore.io.Libcore;
+
/**
* A connection that can make spawn requests.
*/
@@ -193,15 +199,20 @@
new FileOutputStream(descriptors[2]));
}
- int pid;
+ int pid = -1;
+ FileDescriptor childPipeFd = null;
+ FileDescriptor serverPipeFd = null;
try {
parsedArgs = new Arguments(args);
applyUidSecurityPolicy(parsedArgs, peer);
- applyDebuggerSecurityPolicy(parsedArgs);
applyRlimitSecurityPolicy(parsedArgs, peer);
applyCapabilitiesSecurityPolicy(parsedArgs, peer);
+ applyInvokeWithSecurityPolicy(parsedArgs, peer);
+
+ applyDebuggerSystemProperty(parsedArgs);
+ applyInvokeWithSystemProperty(parsedArgs);
int[][] rlimits = null;
@@ -209,25 +220,45 @@
rlimits = parsedArgs.rlimits.toArray(intArray2d);
}
+ if (parsedArgs.runtimeInit && parsedArgs.invokeWith != null) {
+ FileDescriptor[] pipeFds = Libcore.os.pipe();
+ childPipeFd = pipeFds[1];
+ serverPipeFd = pipeFds[0];
+ ZygoteInit.setCloseOnExec(serverPipeFd, true);
+ }
+
pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid,
parsedArgs.gids, parsedArgs.debugFlags, rlimits);
+ } catch (IOException ex) {
+ logAndPrintError(newStderr, "Exception creating pipe", ex);
+ } catch (ErrnoException ex) {
+ logAndPrintError(newStderr, "Exception creating pipe", ex);
} catch (IllegalArgumentException ex) {
- logAndPrintError (newStderr, "Invalid zygote arguments", ex);
- pid = -1;
+ logAndPrintError(newStderr, "Invalid zygote arguments", ex);
} catch (ZygoteSecurityException ex) {
logAndPrintError(newStderr,
"Zygote security policy prevents request: ", ex);
- pid = -1;
}
- if (pid == 0) {
- // in child
- handleChildProc(parsedArgs, descriptors, newStderr);
- // should never happen
- return true;
- } else { /* pid != 0 */
- // in parent...pid of < 0 means failure
- return handleParentProc(pid, descriptors, parsedArgs);
+ try {
+ if (pid == 0) {
+ // in child
+ IoUtils.closeQuietly(serverPipeFd);
+ serverPipeFd = null;
+ handleChildProc(parsedArgs, descriptors, childPipeFd, newStderr);
+
+ // should never get here, the child is expected to either
+ // throw ZygoteInit.MethodAndArgsCaller or exec().
+ return true;
+ } else {
+ // in parent...pid of < 0 means failure
+ IoUtils.closeQuietly(childPipeFd);
+ childPipeFd = null;
+ return handleParentProc(pid, descriptors, serverPipeFd, parsedArgs);
+ }
+ } finally {
+ IoUtils.closeQuietly(childPipeFd);
+ IoUtils.closeQuietly(serverPipeFd);
}
}
@@ -244,8 +275,8 @@
}
/**
- * Handles argument parsing for args related to the zygote spawner.<p>
-
+ * Handles argument parsing for args related to the zygote spawner.
+ *
* Current recognized args:
* <ul>
* <li> --setuid=<i>uid of child process, defaults to 0</i>
@@ -274,6 +305,7 @@
* be handed off to com.android.internal.os.RuntimeInit, rather than
* processed directly
* Android runtime startup (eg, Binder initialization) is also eschewed.
+ * <li> --nice-name=<i>nice name to appear in ps</i>
* <li> If <code>--runtime-init</code> is present:
* [--] <args for RuntimeInit >
* <li> If <code>--runtime-init</code> is absent:
@@ -307,6 +339,9 @@
/** from --runtime-init */
boolean runtimeInit;
+ /** from --nice-name */
+ String niceName;
+
/** from --capabilities */
boolean capabilitiesSpecified;
long permittedCapabilities;
@@ -315,6 +350,9 @@
/** from all --rlimit=r,c,m */
ArrayList<int[]> rlimits;
+ /** from --invoke-with */
+ String invokeWith;
+
/**
* Any args after and including the first non-option arg
* (or after a '--')
@@ -438,6 +476,23 @@
for (int i = params.length - 1; i >= 0 ; i--) {
gids[i] = Integer.parseInt(params[i]);
}
+ } else if (arg.equals("--invoke-with")) {
+ if (invokeWith != null) {
+ throw new IllegalArgumentException(
+ "Duplicate arg specified");
+ }
+ try {
+ invokeWith = args[++curArg];
+ } catch (IndexOutOfBoundsException ex) {
+ throw new IllegalArgumentException(
+ "--invoke-with requires argument");
+ }
+ } else if (arg.startsWith("--nice-name=")) {
+ if (niceName != null) {
+ throw new IllegalArgumentException(
+ "Duplicate arg specified");
+ }
+ niceName = arg.substring(arg.indexOf('=') + 1);
} else {
break;
}
@@ -567,14 +622,15 @@
/**
- * Applies debugger security policy.
+ * Applies debugger system properties to the zygote arguments.
+ *
* If "ro.debuggable" is "1", all apps are debuggable. Otherwise,
* the debugger state is specified via the "--enable-debugger" flag
* in the spawn request.
*
* @param args non-null; zygote spawner args
*/
- private static void applyDebuggerSecurityPolicy(Arguments args) {
+ public static void applyDebuggerSystemProperty(Arguments args) {
if ("1".equals(SystemProperties.get("ro.debuggable"))) {
args.debugFlags |= Zygote.DEBUG_ENABLE_DEBUGGER;
}
@@ -664,12 +720,56 @@
}
/**
+ * Applies zygote security policy.
+ * Based on the credentials of the process issuing a zygote command:
+ * <ol>
+ * <li> uid 0 (root) may specify --invoke-with to launch Zygote with a
+ * wrapper command.
+ * <li> Any other uid may not specify any invoke-with argument.
+ * </ul>
+ *
+ * @param args non-null; zygote spawner arguments
+ * @param peer non-null; peer credentials
+ * @throws ZygoteSecurityException
+ */
+ private static void applyInvokeWithSecurityPolicy(Arguments args, Credentials peer)
+ throws ZygoteSecurityException {
+ int peerUid = peer.getUid();
+
+ if (args.invokeWith != null && peerUid != 0) {
+ throw new ZygoteSecurityException("Peer is not permitted to specify "
+ + "an explicit invoke-with wrapper command");
+ }
+ }
+
+ /**
+ * Applies invoke-with system properties to the zygote arguments.
+ *
+ * @param parsedArgs non-null; zygote args
+ */
+ public static void applyInvokeWithSystemProperty(Arguments args) {
+ if (args.invokeWith == null && args.niceName != null) {
+ if (args.niceName != null) {
+ String property = "wrap." + args.niceName;
+ if (property.length() > 31) {
+ property = property.substring(0, 31);
+ }
+ args.invokeWith = SystemProperties.get(property);
+ if (args.invokeWith != null && args.invokeWith.length() == 0) {
+ args.invokeWith = null;
+ }
+ }
+ }
+ }
+
+ /**
* Handles post-fork setup of child proc, closing sockets as appropriate,
* reopen stdio as appropriate, and ultimately throwing MethodAndArgsCaller
* if successful or returning if failed.
*
* @param parsedArgs non-null; zygote args
* @param descriptors null-ok; new file descriptors for stdio if available.
+ * @param pipeFd null-ok; pipe for communication back to Zygote.
* @param newStderr null-ok; stream to use for stderr until stdio
* is reopened.
*
@@ -677,7 +777,7 @@
* trampoline to code that invokes static main.
*/
private void handleChildProc(Arguments parsedArgs,
- FileDescriptor[] descriptors, PrintStream newStderr)
+ FileDescriptor[] descriptors, FileDescriptor pipeFd, PrintStream newStderr)
throws ZygoteInit.MethodAndArgsCaller {
/*
@@ -704,7 +804,7 @@
descriptors[1], descriptors[2]);
for (FileDescriptor fd: descriptors) {
- ZygoteInit.closeDescriptor(fd);
+ IoUtils.closeQuietly(fd);
}
newStderr = System.err;
} catch (IOException ex) {
@@ -712,37 +812,48 @@
}
}
+ if (parsedArgs.niceName != null) {
+ Process.setArgV0(parsedArgs.niceName);
+ }
+
if (parsedArgs.runtimeInit) {
- RuntimeInit.zygoteInit(parsedArgs.remainingArgs);
- } else {
- ClassLoader cloader;
-
- if (parsedArgs.classpath != null) {
- cloader
- = new PathClassLoader(parsedArgs.classpath,
- ClassLoader.getSystemClassLoader());
+ if (parsedArgs.invokeWith != null) {
+ WrapperInit.execApplication(parsedArgs.invokeWith,
+ parsedArgs.niceName, pipeFd, parsedArgs.remainingArgs);
} else {
- cloader = ClassLoader.getSystemClassLoader();
+ RuntimeInit.zygoteInit(parsedArgs.remainingArgs);
}
-
+ } else {
String className;
try {
className = parsedArgs.remainingArgs[0];
} catch (ArrayIndexOutOfBoundsException ex) {
- logAndPrintError (newStderr,
+ logAndPrintError(newStderr,
"Missing required class name argument", null);
return;
}
- String[] mainArgs
- = new String[parsedArgs.remainingArgs.length - 1];
+ String[] mainArgs = new String[parsedArgs.remainingArgs.length - 1];
System.arraycopy(parsedArgs.remainingArgs, 1,
mainArgs, 0, mainArgs.length);
- try {
- ZygoteInit.invokeStaticMain(cloader, className, mainArgs);
- } catch (RuntimeException ex) {
- logAndPrintError (newStderr, "Error starting. ", ex);
+ if (parsedArgs.invokeWith != null) {
+ WrapperInit.execStandalone(parsedArgs.invokeWith,
+ parsedArgs.classpath, className, mainArgs);
+ } else {
+ ClassLoader cloader;
+ if (parsedArgs.classpath != null) {
+ cloader = new PathClassLoader(parsedArgs.classpath,
+ ClassLoader.getSystemClassLoader());
+ } else {
+ cloader = ClassLoader.getSystemClassLoader();
+ }
+
+ try {
+ ZygoteInit.invokeStaticMain(cloader, className, mainArgs);
+ } catch (RuntimeException ex) {
+ logAndPrintError(newStderr, "Error starting.", ex);
+ }
}
}
}
@@ -754,36 +865,54 @@
* if < 0;
* @param descriptors null-ok; file descriptors for child's new stdio if
* specified.
+ * @param pipeFd null-ok; pipe for communication with child.
* @param parsedArgs non-null; zygote args
* @return true for "exit command loop" and false for "continue command
* loop"
*/
private boolean handleParentProc(int pid,
- FileDescriptor[] descriptors, Arguments parsedArgs) {
+ FileDescriptor[] descriptors, FileDescriptor pipeFd, Arguments parsedArgs) {
- if(pid > 0) {
- // Try to move the new child into the peer's process group.
- try {
- ZygoteInit.setpgid(pid, ZygoteInit.getpgid(peer.getPid()));
- } catch (IOException ex) {
- // This exception is expected in the case where
- // the peer is not in our session
- // TODO get rid of this log message in the case where
- // getsid(0) != getsid(peer.getPid())
- Log.i(TAG, "Zygote: setpgid failed. This is "
- + "normal if peer is not in our session");
+ if (pid > 0) {
+ setChildPgid(pid);
+ }
+
+ if (descriptors != null) {
+ for (FileDescriptor fd: descriptors) {
+ IoUtils.closeQuietly(fd);
}
}
- try {
- if (descriptors != null) {
- for (FileDescriptor fd: descriptors) {
- ZygoteInit.closeDescriptor(fd);
+ if (pipeFd != null && pid > 0) {
+ DataInputStream is = new DataInputStream(new FileInputStream(pipeFd));
+ int innerPid = -1;
+ try {
+ innerPid = is.readInt();
+ } catch (IOException ex) {
+ Log.w(TAG, "Error reading pid from wrapped process, child may have died", ex);
+ } finally {
+ try {
+ is.close();
+ } catch (IOException ex) {
}
}
- } catch (IOException ex) {
- Log.e(TAG, "Error closing passed descriptors in "
- + "parent process", ex);
+
+ // Ensure that the pid reported by the wrapped process is either the
+ // child process that we forked, or a descendant of it.
+ if (innerPid > 0) {
+ int parentPid = innerPid;
+ while (parentPid > 0 && parentPid != pid) {
+ parentPid = Process.getParentPid(parentPid);
+ }
+ if (parentPid > 0) {
+ Log.i(TAG, "Wrapped process has pid " + innerPid);
+ pid = innerPid;
+ } else {
+ Log.w(TAG, "Wrapped process reported a pid that is not a child of "
+ + "the process that we forked: childPid=" + pid
+ + " innerPid=" + innerPid);
+ }
+ }
}
try {
@@ -808,6 +937,20 @@
return false;
}
+ private void setChildPgid(int pid) {
+ // Try to move the new child into the peer's process group.
+ try {
+ ZygoteInit.setpgid(pid, ZygoteInit.getpgid(peer.getPid()));
+ } catch (IOException ex) {
+ // This exception is expected in the case where
+ // the peer is not in our session
+ // TODO get rid of this log message in the case where
+ // getsid(0) != getsid(peer.getPid())
+ Log.i(TAG, "Zygote: setpgid failed. This is "
+ + "normal if peer is not in our session");
+ }
+ }
+
/**
* Logs an error message and prints it to the specified stream, if
* provided
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index fbe66e5..157c0bf 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -23,6 +23,7 @@
import android.net.LocalServerSocket;
import android.os.Debug;
import android.os.FileUtils;
+import android.os.Process;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.util.EventLog;
@@ -68,7 +69,7 @@
private static final int PRELOAD_GC_THRESHOLD = 50000;
public static final String USAGE_STRING =
- " <\"true\"|\"false\" for startSystemServer>";
+ " <\"start-system-server\"|\"\" for startSystemServer>";
private static LocalServerSocket sServerSocket;
@@ -441,11 +442,20 @@
// set umask to 0077 so new files and directories will default to owner-only permissions.
FileUtils.setUMask(FileUtils.S_IRWXG | FileUtils.S_IRWXO);
- /*
- * Pass the remaining arguments to SystemServer.
- * "--nice-name=system_server com.android.server.SystemServer"
- */
- RuntimeInit.zygoteInit(parsedArgs.remainingArgs);
+ if (parsedArgs.niceName != null) {
+ Process.setArgV0(parsedArgs.niceName);
+ }
+
+ if (parsedArgs.invokeWith != null) {
+ WrapperInit.execApplication(parsedArgs.invokeWith,
+ parsedArgs.niceName, null, parsedArgs.remainingArgs);
+ } else {
+ /*
+ * Pass the remaining arguments to SystemServer.
+ */
+ RuntimeInit.zygoteInit(parsedArgs.remainingArgs);
+ }
+
/* should never reach here */
}
@@ -470,20 +480,13 @@
try {
parsedArgs = new ZygoteConnection.Arguments(args);
-
- /*
- * Enable debugging of the system process if *either* the command line flags
- * indicate it should be debuggable or the ro.debuggable system property
- * is set to "1"
- */
- int debugFlags = parsedArgs.debugFlags;
- if ("1".equals(SystemProperties.get("ro.debuggable")))
- debugFlags |= Zygote.DEBUG_ENABLE_DEBUGGER;
+ ZygoteConnection.applyDebuggerSystemProperty(parsedArgs);
+ ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs);
/* Request to fork the system server process */
pid = Zygote.forkSystemServer(
parsedArgs.uid, parsedArgs.gid,
- parsedArgs.gids, debugFlags, null,
+ parsedArgs.gids, parsedArgs.debugFlags, null,
parsedArgs.permittedCapabilities,
parsedArgs.effectiveCapabilities);
} catch (IllegalArgumentException ex) {
@@ -522,9 +525,9 @@
throw new RuntimeException(argv[0] + USAGE_STRING);
}
- if (argv[1].equals("true")) {
+ if (argv[1].equals("start-system-server")) {
startSystemServer();
- } else if (!argv[1].equals("false")) {
+ } else if (!argv[1].equals("")) {
throw new RuntimeException(argv[0] + USAGE_STRING);
}
@@ -696,15 +699,6 @@
FileDescriptor out, FileDescriptor err) throws IOException;
/**
- * Calls close() on a file descriptor
- *
- * @param fd descriptor to close
- * @throws IOException
- */
- static native void closeDescriptor(FileDescriptor fd)
- throws IOException;
-
- /**
* Toggles the close-on-exec flag for the specified file descriptor.
*
* @param fd non-null; file descriptor
diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java
index f1887eb..ff04735 100644
--- a/core/java/com/android/internal/widget/ActionBarView.java
+++ b/core/java/com/android/internal/widget/ActionBarView.java
@@ -78,7 +78,7 @@
private static final int DEFAULT_CUSTOM_GRAVITY = Gravity.LEFT | Gravity.CENTER_VERTICAL;
- private final int mContentHeight;
+ private int mContentHeight;
private int mNavigationMode;
private int mDisplayOptions = ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_HOME_AS_UP;
@@ -95,8 +95,7 @@
private TextView mSubtitleView;
private Spinner mSpinner;
private LinearLayout mListNavLayout;
- private HorizontalScrollView mTabScrollView;
- private ViewGroup mTabLayout;
+ private ScrollingTabContainerView mTabScrollView;
private View mCustomNavView;
private ProgressBar mProgressView;
private ProgressBar mIndeterminateProgressView;
@@ -122,6 +121,8 @@
private SpinnerAdapter mSpinnerAdapter;
private OnNavigationListener mCallback;
+ private Runnable mTabSelector;
+
private final AdapterView.OnItemSelectedListener mNavItemSelectedListener =
new AdapterView.OnItemSelectedListener() {
public void onItemSelected(AdapterView parent, View view, int position, long id) {
@@ -199,8 +200,6 @@
mProgressBarPadding = a.getDimensionPixelOffset(R.styleable.ActionBar_progressBarPadding, 0);
mItemPadding = a.getDimensionPixelOffset(R.styleable.ActionBar_itemPadding, 0);
- mIncludeTabs = a.getBoolean(R.styleable.ActionBar_embeddedTabs, true);
-
setDisplayOptions(a.getInt(R.styleable.ActionBar_displayOptions, DISPLAY_DEFAULT));
final int customNavId = a.getResourceId(R.styleable.ActionBar_customNavigationLayout, 0);
@@ -229,6 +228,12 @@
}
@Override
+ public void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ removeCallbacks(mTabSelector);
+ }
+
+ @Override
public boolean shouldDelayChildPressedState() {
return false;
}
@@ -247,6 +252,11 @@
addView(mIndeterminateProgressView);
}
+ public void setContentHeight(int height) {
+ mContentHeight = height;
+ requestLayout();
+ }
+
public void setSplitActionBar(boolean splitActionBar) {
if (mSplitActionBar != splitActionBar) {
if (mMenuView != null) {
@@ -271,8 +281,9 @@
return mIncludeTabs;
}
- public void setExternalTabLayout(ViewGroup tabLayout) {
- mTabLayout = tabLayout;
+ public void setEmbeddedTabView(ScrollingTabContainerView tabs) {
+ mTabScrollView = tabs;
+ mIncludeTabs = tabs != null;
}
public void setCallback(OnNavigationListener callback) {
@@ -489,7 +500,7 @@
}
break;
case ActionBar.NAVIGATION_MODE_TABS:
- if (mTabScrollView != null) {
+ if (mTabScrollView != null && mIncludeTabs) {
removeView(mTabScrollView);
}
}
@@ -513,8 +524,7 @@
addView(mListNavLayout);
break;
case ActionBar.NAVIGATION_MODE_TABS:
- ensureTabsExist();
- if (mTabScrollView != null) {
+ if (mTabScrollView != null && mIncludeTabs) {
addView(mTabScrollView);
}
break;
@@ -523,24 +533,17 @@
requestLayout();
}
}
-
- private void ensureTabsExist() {
- if (!mIncludeTabs) return;
- if (mTabScrollView == null) {
- mTabScrollView = new HorizontalScrollView(getContext());
- mTabScrollView.setHorizontalFadingEdgeEnabled(true);
- mTabLayout = createTabContainer();
- mTabScrollView.addView(mTabLayout);
- }
- }
-
- public ViewGroup createTabContainer() {
- ViewGroup result = new LinearLayout(getContext(), null,
+ public ScrollingTabContainerView createTabContainer() {
+ final LinearLayout tabLayout = new LinearLayout(getContext(), null,
com.android.internal.R.attr.actionBarTabBarStyle);
- result.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
- mContentHeight));
- return result;
+ tabLayout.setMeasureWithLargestChildEnabled(true);
+ tabLayout.setLayoutParams(new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.WRAP_CONTENT, mContentHeight));
+
+ final ScrollingTabContainerView scroller = new ScrollingTabContainerView(mContext);
+ scroller.setTabLayout(tabLayout);
+ return scroller;
}
public void setDropdownAdapter(SpinnerAdapter adapter) {
@@ -574,51 +577,6 @@
return mDisplayOptions;
}
- private TabView createTabView(ActionBar.Tab tab) {
- final TabView tabView = new TabView(getContext(), tab);
- tabView.setFocusable(true);
-
- if (mTabClickListener == null) {
- mTabClickListener = new TabClickListener();
- }
- tabView.setOnClickListener(mTabClickListener);
- return tabView;
- }
-
- public void addTab(ActionBar.Tab tab, boolean setSelected) {
- ensureTabsExist();
- View tabView = createTabView(tab);
- mTabLayout.addView(tabView);
- if (setSelected) {
- tabView.setSelected(true);
- }
- }
-
- public void addTab(ActionBar.Tab tab, int position, boolean setSelected) {
- ensureTabsExist();
- final TabView tabView = createTabView(tab);
- mTabLayout.addView(tabView, position);
- if (setSelected) {
- tabView.setSelected(true);
- }
- }
-
- public void updateTab(int position) {
- ((TabView) mTabLayout.getChildAt(position)).update();
- }
-
- public void removeTabAt(int position) {
- if (mTabLayout != null) {
- mTabLayout.removeViewAt(position);
- }
- }
-
- public void removeAllTabs() {
- if (mTabLayout != null) {
- mTabLayout.removeAllViews();
- }
- }
-
@Override
protected LayoutParams generateDefaultLayoutParams() {
// Used by custom nav views if they don't supply layout params. Everything else
@@ -667,15 +625,6 @@
addView(mTitleLayout);
}
- public void setTabSelected(int position) {
- ensureTabsExist();
- final int tabCount = mTabLayout.getChildCount();
- for (int i = 0; i < tabCount; i++) {
- final View child = mTabLayout.getChildAt(i);
- child.setSelected(i == position);
- }
- }
-
public void setContextView(ActionBarContextView view) {
mContextView = view;
}
@@ -948,97 +897,6 @@
}
}
- private static class TabView extends LinearLayout {
- private ActionBar.Tab mTab;
- private TextView mTextView;
- private ImageView mIconView;
- private View mCustomView;
-
- public TabView(Context context, ActionBar.Tab tab) {
- super(context, null, com.android.internal.R.attr.actionBarTabStyle);
- mTab = tab;
-
- update();
-
- setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
- LayoutParams.MATCH_PARENT, 1));
- }
-
- public void update() {
- final ActionBar.Tab tab = mTab;
- final View custom = tab.getCustomView();
- if (custom != null) {
- addView(custom);
- mCustomView = custom;
- if (mTextView != null) mTextView.setVisibility(GONE);
- if (mIconView != null) {
- mIconView.setVisibility(GONE);
- mIconView.setImageDrawable(null);
- }
- } else {
- if (mCustomView != null) {
- removeView(mCustomView);
- mCustomView = null;
- }
-
- final Drawable icon = tab.getIcon();
- final CharSequence text = tab.getText();
-
- if (icon != null) {
- if (mIconView == null) {
- ImageView iconView = new ImageView(getContext());
- LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,
- LayoutParams.WRAP_CONTENT);
- lp.gravity = Gravity.CENTER_VERTICAL;
- iconView.setLayoutParams(lp);
- addView(iconView, 0);
- mIconView = iconView;
- }
- mIconView.setImageDrawable(icon);
- mIconView.setVisibility(VISIBLE);
- } else if (mIconView != null) {
- mIconView.setVisibility(GONE);
- mIconView.setImageDrawable(null);
- }
-
- if (text != null) {
- if (mTextView == null) {
- TextView textView = new TextView(getContext(), null,
- com.android.internal.R.attr.actionBarTabTextStyle);
- textView.setSingleLine();
- textView.setEllipsize(TruncateAt.END);
- LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,
- LayoutParams.WRAP_CONTENT);
- lp.gravity = Gravity.CENTER_VERTICAL;
- textView.setLayoutParams(lp);
- addView(textView);
- mTextView = textView;
- }
- mTextView.setText(text);
- mTextView.setVisibility(VISIBLE);
- } else {
- mTextView.setVisibility(GONE);
- }
- }
- }
-
- public ActionBar.Tab getTab() {
- return mTab;
- }
- }
-
- private class TabClickListener implements OnClickListener {
- public void onClick(View view) {
- TabView tabView = (TabView) view;
- tabView.getTab().select();
- final int tabCount = mTabLayout.getChildCount();
- for (int i = 0; i < tabCount; i++) {
- final View child = mTabLayout.getChildAt(i);
- child.setSelected(child == view);
- }
- }
- }
-
private static class HomeView extends FrameLayout {
private View mUpView;
private View mIconView;
diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java
index d789584..bf1c637 100644
--- a/core/java/com/android/internal/widget/PointerLocationView.java
+++ b/core/java/com/android/internal/widget/PointerLocationView.java
@@ -385,6 +385,7 @@
.append(" ToolMinor=").append(coords.toolMinor, 3)
.append(" Orientation=").append((float)(coords.orientation * 180 / Math.PI), 1)
.append("deg")
+ .append(" Distance=").append(coords.getAxisValue(MotionEvent.AXIS_DISTANCE), 1)
.append(" VScroll=").append(coords.getAxisValue(MotionEvent.AXIS_VSCROLL), 1)
.append(" HScroll=").append(coords.getAxisValue(MotionEvent.AXIS_HSCROLL), 1)
.append(" ToolType=").append(MotionEvent.toolTypeToString(toolType))
diff --git a/core/java/com/android/internal/widget/ScrollingTabContainerView.java b/core/java/com/android/internal/widget/ScrollingTabContainerView.java
new file mode 100644
index 0000000..c7d37f2
--- /dev/null
+++ b/core/java/com/android/internal/widget/ScrollingTabContainerView.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget;
+
+import android.app.ActionBar;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.text.TextUtils.TruncateAt;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.HorizontalScrollView;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+public class ScrollingTabContainerView extends HorizontalScrollView {
+ Runnable mTabSelector;
+ private TabClickListener mTabClickListener;
+
+ private LinearLayout mTabLayout;
+
+ int mMaxTabWidth;
+
+ public ScrollingTabContainerView(Context context) {
+ super(context);
+ setHorizontalScrollBarEnabled(false);
+ }
+
+ @Override
+ public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ setFillViewport(widthMode == MeasureSpec.EXACTLY);
+
+ final int childCount = getChildCount();
+ if (childCount > 1 &&
+ (widthMode == MeasureSpec.EXACTLY || widthMode == MeasureSpec.AT_MOST)) {
+ if (childCount > 2) {
+ mMaxTabWidth = (int) (MeasureSpec.getSize(widthMeasureSpec) * 0.4f);
+ } else {
+ mMaxTabWidth = MeasureSpec.getSize(widthMeasureSpec) / 2;
+ }
+ } else {
+ mMaxTabWidth = -1;
+ }
+
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ public void setTabSelected(int position) {
+ if (mTabLayout == null) {
+ return;
+ }
+
+ final int tabCount = mTabLayout.getChildCount();
+ for (int i = 0; i < tabCount; i++) {
+ final View child = mTabLayout.getChildAt(i);
+ final boolean isSelected = i == position;
+ child.setSelected(isSelected);
+ if (isSelected) {
+ animateToTab(position);
+ }
+ }
+ }
+
+ public void animateToTab(int position) {
+ final View tabView = mTabLayout.getChildAt(position);
+ if (mTabSelector != null) {
+ removeCallbacks(mTabSelector);
+ }
+ mTabSelector = new Runnable() {
+ public void run() {
+ final int scrollPos = tabView.getLeft() - (getWidth() - tabView.getWidth()) / 2;
+ smoothScrollTo(scrollPos, 0);
+ mTabSelector = null;
+ }
+ };
+ post(mTabSelector);
+ }
+
+ public void setTabLayout(LinearLayout tabLayout) {
+ if (mTabLayout != tabLayout) {
+ if (mTabLayout != null) {
+ ((ViewGroup) mTabLayout.getParent()).removeView(mTabLayout);
+ }
+ if (tabLayout != null) {
+ addView(tabLayout);
+ }
+ mTabLayout = tabLayout;
+ }
+ }
+
+ public LinearLayout getTabLayout() {
+ return mTabLayout;
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ if (mTabSelector != null) {
+ removeCallbacks(mTabSelector);
+ }
+ }
+
+ private TabView createTabView(ActionBar.Tab tab) {
+ final TabView tabView = new TabView(getContext(), tab);
+ tabView.setFocusable(true);
+
+ if (mTabClickListener == null) {
+ mTabClickListener = new TabClickListener();
+ }
+ tabView.setOnClickListener(mTabClickListener);
+ return tabView;
+ }
+
+ public void addTab(ActionBar.Tab tab, boolean setSelected) {
+ View tabView = createTabView(tab);
+ mTabLayout.addView(tabView, new LinearLayout.LayoutParams(0,
+ LayoutParams.MATCH_PARENT, 1));
+ if (setSelected) {
+ tabView.setSelected(true);
+ }
+ }
+
+ public void addTab(ActionBar.Tab tab, int position, boolean setSelected) {
+ final TabView tabView = createTabView(tab);
+ mTabLayout.addView(tabView, position, new LinearLayout.LayoutParams(
+ 0, LayoutParams.MATCH_PARENT, 1));
+ if (setSelected) {
+ tabView.setSelected(true);
+ }
+ }
+
+ public void updateTab(int position) {
+ ((TabView) mTabLayout.getChildAt(position)).update();
+ }
+
+ public void removeTabAt(int position) {
+ if (mTabLayout != null) {
+ mTabLayout.removeViewAt(position);
+ }
+ }
+
+ public void removeAllTabs() {
+ if (mTabLayout != null) {
+ mTabLayout.removeAllViews();
+ }
+ }
+
+ private class TabView extends LinearLayout {
+ private ActionBar.Tab mTab;
+ private TextView mTextView;
+ private ImageView mIconView;
+ private View mCustomView;
+
+ public TabView(Context context, ActionBar.Tab tab) {
+ super(context, null, com.android.internal.R.attr.actionBarTabStyle);
+ mTab = tab;
+
+ update();
+ }
+
+ @Override
+ public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ // Re-measure if we went beyond our maximum size.
+ if (mMaxTabWidth > 0 && getMeasuredWidth() > mMaxTabWidth) {
+ super.onMeasure(MeasureSpec.makeMeasureSpec(mMaxTabWidth, MeasureSpec.EXACTLY),
+ heightMeasureSpec);
+ }
+ }
+
+ public void update() {
+ final ActionBar.Tab tab = mTab;
+ final View custom = tab.getCustomView();
+ if (custom != null) {
+ addView(custom);
+ mCustomView = custom;
+ if (mTextView != null) mTextView.setVisibility(GONE);
+ if (mIconView != null) {
+ mIconView.setVisibility(GONE);
+ mIconView.setImageDrawable(null);
+ }
+ } else {
+ if (mCustomView != null) {
+ removeView(mCustomView);
+ mCustomView = null;
+ }
+
+ final Drawable icon = tab.getIcon();
+ final CharSequence text = tab.getText();
+
+ if (icon != null) {
+ if (mIconView == null) {
+ ImageView iconView = new ImageView(getContext());
+ LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,
+ LayoutParams.WRAP_CONTENT);
+ lp.gravity = Gravity.CENTER_VERTICAL;
+ iconView.setLayoutParams(lp);
+ addView(iconView, 0);
+ mIconView = iconView;
+ }
+ mIconView.setImageDrawable(icon);
+ mIconView.setVisibility(VISIBLE);
+ } else if (mIconView != null) {
+ mIconView.setVisibility(GONE);
+ mIconView.setImageDrawable(null);
+ }
+
+ if (text != null) {
+ if (mTextView == null) {
+ TextView textView = new TextView(getContext(), null,
+ com.android.internal.R.attr.actionBarTabTextStyle);
+ textView.setSingleLine();
+ textView.setEllipsize(TruncateAt.END);
+ LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,
+ LayoutParams.WRAP_CONTENT);
+ lp.gravity = Gravity.CENTER_VERTICAL;
+ textView.setLayoutParams(lp);
+ addView(textView);
+ mTextView = textView;
+ }
+ mTextView.setText(text);
+ mTextView.setVisibility(VISIBLE);
+ } else {
+ mTextView.setVisibility(GONE);
+ }
+ }
+ }
+
+ public ActionBar.Tab getTab() {
+ return mTab;
+ }
+ }
+
+ private class TabClickListener implements OnClickListener {
+ public void onClick(View view) {
+ TabView tabView = (TabView) view;
+ tabView.getTab().select();
+ final int tabCount = mTabLayout.getChildCount();
+ for (int i = 0; i < tabCount; i++) {
+ final View child = mTabLayout.getChildAt(i);
+ child.setSelected(child == view);
+ }
+ }
+ }
+}
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index b787e9f..e610640 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -808,8 +808,11 @@
* Start the Android runtime. This involves starting the virtual machine
* and calling the "static void main(String[] args)" method in the class
* named by "className".
+ *
+ * Passes the main function two arguments, the class name and the specified
+ * options string.
*/
-void AndroidRuntime::start(const char* className, const bool startSystemServer)
+void AndroidRuntime::start(const char* className, const char* options)
{
LOGD("\n>>>>>> AndroidRuntime START %s <<<<<<\n",
className != NULL ? className : "(unknown)");
@@ -820,7 +823,7 @@
* 'startSystemServer == true' means runtime is obsolete and not run from
* init.rc anymore, so we print out the boot start event here.
*/
- if (startSystemServer) {
+ if (strcmp(options, "start-system-server") == 0) {
/* track our progress through the boot sequence */
const int LOG_BOOT_PROGRESS_START = 3000;
LOG_EVENT_LONG(LOG_BOOT_PROGRESS_START,
@@ -857,13 +860,13 @@
/*
* We want to call main() with a String array with arguments in it.
- * At present we only have one argument, the class name. Create an
- * array to hold it.
+ * At present we have two arguments, the class name and an option string.
+ * Create an array to hold them.
*/
jclass stringClass;
jobjectArray strArray;
jstring classNameStr;
- jstring startSystemServerStr;
+ jstring optionsStr;
stringClass = env->FindClass("java/lang/String");
assert(stringClass != NULL);
@@ -872,9 +875,8 @@
classNameStr = env->NewStringUTF(className);
assert(classNameStr != NULL);
env->SetObjectArrayElement(strArray, 0, classNameStr);
- startSystemServerStr = env->NewStringUTF(startSystemServer ?
- "true" : "false");
- env->SetObjectArrayElement(strArray, 1, startSystemServerStr);
+ optionsStr = env->NewStringUTF(options);
+ env->SetObjectArrayElement(strArray, 1, optionsStr);
/*
* Start VM. This thread becomes the main thread of the VM, and will
@@ -909,12 +911,6 @@
LOGW("Warning: VM did not shut down cleanly\n");
}
-void AndroidRuntime::start()
-{
- start("com.android.internal.os.RuntimeInit",
- false /* Don't start the system server */);
-}
-
void AndroidRuntime::onExit(int code)
{
LOGV("AndroidRuntime onExit calling exit(%d)", code);
diff --git a/core/jni/com_android_internal_os_ZygoteInit.cpp b/core/jni/com_android_internal_os_ZygoteInit.cpp
index e627e4a..86fd9cb 100644
--- a/core/jni/com_android_internal_os_ZygoteInit.cpp
+++ b/core/jni/com_android_internal_os_ZygoteInit.cpp
@@ -131,28 +131,6 @@
} while (err < 0 && errno == EINTR);
}
-static void com_android_internal_os_ZygoteInit_closeDescriptor(JNIEnv* env,
- jobject clazz, jobject descriptor)
-{
- int fd;
- int err;
-
- fd = jniGetFDFromFileDescriptor(env, descriptor);
-
- if (env->ExceptionOccurred() != NULL) {
- return;
- }
-
- do {
- err = close(fd);
- } while (err < 0 && errno == EINTR);
-
- if (err < 0) {
- jniThrowIOException(env, errno);
- return;
- }
-}
-
static void com_android_internal_os_ZygoteInit_setCloseOnExec (JNIEnv *env,
jobject clazz, jobject descriptor, jboolean flag)
{
@@ -332,8 +310,6 @@
"(Ljava/io/FileDescriptor;Ljava/io/FileDescriptor;"
"Ljava/io/FileDescriptor;)V",
(void *) com_android_internal_os_ZygoteInit_reopenStdio},
- { "closeDescriptor", "(Ljava/io/FileDescriptor;)V",
- (void *) com_android_internal_os_ZygoteInit_closeDescriptor},
{ "setCloseOnExec", "(Ljava/io/FileDescriptor;Z)V",
(void *) com_android_internal_os_ZygoteInit_setCloseOnExec},
{ "setCapabilities", "(JJ)V",
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 6c18089..c1e81c3 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -823,6 +823,10 @@
Default value is false. EditText content is always selectable. -->
<attr name="textIsSelectable" format="boolean" />
+ <!-- When true, IME suggestions will be displayed when the user double taps on editable text.
+ The default value is true. -->
+ <attr name="suggestionsEnabled" format="boolean" />
+
<!-- Where to ellipsize text. -->
<attr name="ellipsize">
<enum name="none" value="0" />
@@ -2877,6 +2881,8 @@
<!-- Indicates that the content of a non-editable text can be selected. -->
<attr name="textIsSelectable" />
+ <!-- Suggestions will be displayed when the user double taps on editable text. -->
+ <attr name="suggestionsEnabled" />
</declare-styleable>
<!-- An <code>input-extras</code> is a container for extra data to supply to
an input method. Contains
@@ -4907,9 +4913,6 @@
<!-- Specifies padding that should be applied to the left and right sides of
system-provided items in the bar. -->
<attr name="itemPadding" format="dimension" />
- <!-- Specifies whether tabs should be embedded within the bar itself (true)
- or displayed elsewhere (false). -->
- <attr name="embeddedTabs" format="boolean" />
</declare-styleable>
<declare-styleable name="ActionMode">
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 8ad8f67..1957b2a 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1670,5 +1670,6 @@
<public type="attr" name="horizontalDirection" />
<public type="attr" name="fullBackupAgent" />
+ <public type="attr" name="suggestionsEnabled" />
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index ccb3518..816546b 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1852,7 +1852,7 @@
<!-- Do not translate. WebView User Agent string -->
<string name="web_user_agent" translatable="false">Mozilla/5.0 (Linux; U; <xliff:g id="x">Android %s</xliff:g>)
- AppleWebKit/534.16 (KHTML, like Gecko) Version/4.0 <xliff:g id="mobile">%s</xliff:g>Safari/534.16</string>
+ AppleWebKit/534.20 (KHTML, like Gecko) Version/4.0 <xliff:g id="mobile">%s</xliff:g>Safari/534.20</string>
<!-- Do not translate. WebView User Agent targeted content -->
<string name="web_user_agent_target_content" translatable="false">"Mobile "</string>
@@ -1998,6 +1998,9 @@
<!-- Do not translate. Regex used by AutoFill. -->
<string name="autofill_card_ignored_re">^card</string>
+ <!-- Do not translate. Regex used by AutoFill. -->
+ <string name="autofill_fax_re">fax<!-- fr-FR -->|télécopie|telecopie<!-- ja-JP -->|ファックス<!-- ru -->|факс<!-- zh-CN -->|传真<!-- zh-TW -->|傳真</string>
+
<!-- Title of an application permission, listed so the user can choose whether
they want to allow the application to do this. -->
<string name="permlab_readHistoryBookmarks">read Browser\'s history and bookmarks</string>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 26b5d95..e95094f0 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1078,7 +1078,6 @@
<item name="android:progressBarStyle">@android:style/Widget.ProgressBar.Horizontal</item>
<item name="android:indeterminateProgressStyle">@android:style/Widget.ProgressBar.Small</item>
<item name="android:homeLayout">@android:layout/action_bar_home</item>
- <item name="android:embeddedTabs">@android:bool/action_bar_embed_tabs</item>
</style>
<style name="Widget.ActionMode">
@@ -1122,6 +1121,7 @@
</style>
<style name="Widget.ActionBarView_TabView">
+ <item name="android:gravity">center_horizontal</item>
<item name="android:background">@drawable/minitab_lt</item>
<item name="android:paddingLeft">4dip</item>
<item name="android:paddingRight">4dip</item>
@@ -1795,6 +1795,9 @@
</style>
<style name="Widget.Holo.ActionBarView_TabBar" parent="Widget.ActionBarView_TabBar">
+ <item name="android:divider">?android:attr/dividerVertical</item>
+ <item name="android:showDividers">middle</item>
+ <item name="android:dividerPadding">8dip</item>
</style>
<style name="Widget.Holo.ActionBarView_TabText" parent="Widget.ActionBarView_TabText">
diff --git a/include/android_runtime/AndroidRuntime.h b/include/android_runtime/AndroidRuntime.h
index b02a057..32cd4f5 100644
--- a/include/android_runtime/AndroidRuntime.h
+++ b/include/android_runtime/AndroidRuntime.h
@@ -39,6 +39,13 @@
AndroidRuntime();
virtual ~AndroidRuntime();
+ enum StartMode {
+ Zygote,
+ SystemServer,
+ Application,
+ Tool,
+ };
+
/**
* Register a set of methods in the specified class.
*/
@@ -59,8 +66,7 @@
int addVmArguments(int argc, const char* const argv[]);
- void start(const char *classname, const bool startSystemServer);
- void start(); // start in android.util.RuntimeInit
+ void start(const char *classname, const char* options);
static AndroidRuntime* getRuntime();
diff --git a/include/utils/ResourceTypes.h b/include/utils/ResourceTypes.h
index 9e4e132..2c7cf75 100644
--- a/include/utils/ResourceTypes.h
+++ b/include/utils/ResourceTypes.h
@@ -953,6 +953,7 @@
UI_MODE_TYPE_NORMAL = ACONFIGURATION_UI_MODE_TYPE_NORMAL,
UI_MODE_TYPE_DESK = ACONFIGURATION_UI_MODE_TYPE_DESK,
UI_MODE_TYPE_CAR = ACONFIGURATION_UI_MODE_TYPE_CAR,
+ UI_MODE_TYPE_TELEVISION = ACONFIGURATION_UI_MODE_TYPE_TELEVISION,
// uiMode bits for the night switch.
MASK_UI_MODE_NIGHT = 0x30,
diff --git a/keystore/java/android/security/IKeyChainAliasResponse.aidl b/keystore/java/android/security/IKeyChainAliasResponse.aidl
new file mode 100644
index 0000000..e042001
--- /dev/null
+++ b/keystore/java/android/security/IKeyChainAliasResponse.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.security;
+
+/**
+ * Used by the {@code KeyChainActivity} to return alias for {@link KeyStore#chooseAlias}.
+ *
+ * @hide
+ */
+interface IKeyChainAliasResponse {
+
+ void alias(String alias);
+}
diff --git a/keystore/java/android/security/IKeyChainService.aidl b/keystore/java/android/security/IKeyChainService.aidl
index be59f23..2763e46 100644
--- a/keystore/java/android/security/IKeyChainService.aidl
+++ b/keystore/java/android/security/IKeyChainService.aidl
@@ -15,8 +15,6 @@
*/
package android.security;
-import android.os.Bundle;
-
/**
* Caller is required to ensure that {@link KeyStore#unlock
* KeyStore.unlock} was successful.
diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java
index 08e05ef..ec820cf 100644
--- a/keystore/java/android/security/KeyChain.java
+++ b/keystore/java/android/security/KeyChain.java
@@ -17,9 +17,11 @@
import android.accounts.Account;
import android.accounts.AccountManager;
+import android.accounts.AccountManagerCallback;
import android.accounts.AccountManagerFuture;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
+import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -56,64 +58,123 @@
public static final String ACCOUNT_TYPE = "com.android.keychain";
/**
- * Returns an {@code Intent} for use with {@link
- * android.app.Activity#startActivityForResult
- * startActivityForResult}. The result will be returned via {@link
- * android.app.Activity#onActivityResult onActivityResult} with
- * {@link android.app.Activity#RESULT_OK RESULT_OK} and the alias
- * in the returned {@code Intent}'s extra data with key {@link
- * android.content.Intent#EXTRA_TEXT Intent.EXTRA_TEXT}.
+ * @hide Also used by KeyChainActivity implementation
*/
- public static Intent chooseAlias() {
- return new Intent("com.android.keychain.CHOOSER");
+ public static final String EXTRA_RESPONSE = "response";
+
+ /**
+ * Launches an {@code Activity} for the user to select the alias
+ * for a private key and certificate pair for authentication. The
+ * selected alias or null will be returned via the
+ * IKeyChainAliasResponse callback.
+ */
+ public static void choosePrivateKeyAlias(Activity activity, KeyChainAliasResponse response) {
+ if (activity == null) {
+ throw new NullPointerException("activity == null");
+ }
+ if (response == null) {
+ throw new NullPointerException("response == null");
+ }
+ Intent intent = new Intent("com.android.keychain.CHOOSER");
+ intent.putExtra(EXTRA_RESPONSE, new AliasResponse(activity, response));
+ activity.startActivity(intent);
+ }
+
+ private static class AliasResponse extends IKeyChainAliasResponse.Stub {
+ private final Activity activity;
+ private final KeyChainAliasResponse keyChainAliasResponse;
+ private AliasResponse(Activity activity, KeyChainAliasResponse keyChainAliasResponse) {
+ this.activity = activity;
+ this.keyChainAliasResponse = keyChainAliasResponse;
+ }
+ @Override public void alias(String alias) {
+ if (alias == null) {
+ keyChainAliasResponse.alias(null);
+ return;
+ }
+ AccountManager accountManager = AccountManager.get(activity);
+ accountManager.getAuthToken(getAccount(activity),
+ alias,
+ null,
+ activity,
+ new AliasAccountManagerCallback(keyChainAliasResponse,
+ alias),
+ null);
+ }
+ }
+
+ private static class AliasAccountManagerCallback implements AccountManagerCallback<Bundle> {
+ private final KeyChainAliasResponse keyChainAliasResponse;
+ private final String alias;
+ private AliasAccountManagerCallback(KeyChainAliasResponse keyChainAliasResponse,
+ String alias) {
+ this.keyChainAliasResponse = keyChainAliasResponse;
+ this.alias = alias;
+ }
+ @Override public void run(AccountManagerFuture<Bundle> future) {
+ Bundle bundle;
+ try {
+ bundle = future.getResult();
+ } catch (OperationCanceledException e) {
+ keyChainAliasResponse.alias(null);
+ return;
+ } catch (IOException e) {
+ keyChainAliasResponse.alias(null);
+ return;
+ } catch (AuthenticatorException e) {
+ keyChainAliasResponse.alias(null);
+ return;
+ }
+ String authToken = bundle.getString(AccountManager.KEY_AUTHTOKEN);
+ if (authToken != null) {
+ keyChainAliasResponse.alias(alias);
+ } else {
+ keyChainAliasResponse.alias(null);
+ }
+ }
}
/**
- * Returns a new {@code KeyChainResult} instance.
+ * Returns the {@code PrivateKey} for the requested alias, or null
+ * if no there is no result.
*/
- public static KeyChainResult get(Context context, String alias)
+ public static PrivateKey getPrivateKey(Context context, String alias)
throws InterruptedException, RemoteException {
if (alias == null) {
throw new NullPointerException("alias == null");
}
KeyChainConnection keyChainConnection = bind(context);
try {
- // Account is created if necessary during binding of the IKeyChainService
- AccountManager accountManager = AccountManager.get(context);
- Account account = accountManager.getAccountsByType(ACCOUNT_TYPE)[0];
- AccountManagerFuture<Bundle> future = accountManager.getAuthToken(account,
- alias,
- false,
- null,
- null);
- Bundle bundle;
- try {
- bundle = future.getResult();
- } catch (OperationCanceledException e) {
- throw new AssertionError(e);
- } catch (IOException e) {
- throw new AssertionError(e);
- } catch (AuthenticatorException e) {
- throw new AssertionError(e);
- }
- Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT);
- if (intent != null) {
- Bundle result = new Bundle();
- // we don't want this Eclair compatability flag,
- // it will prevent onActivityResult from being called
- intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_NEW_TASK);
- return new KeyChainResult(intent);
- }
-
- String authToken = bundle.getString(AccountManager.KEY_AUTHTOKEN);
+ String authToken = authToken(context, alias);
if (authToken == null) {
- throw new AssertionError("Invalid authtoken");
+ return null;
}
IKeyChainService keyChainService = keyChainConnection.getService();
byte[] privateKeyBytes = keyChainService.getPrivateKey(alias, authToken);
+ return toPrivateKey(privateKeyBytes);
+ } finally {
+ keyChainConnection.close();
+ }
+ }
+
+ /**
+ * Returns the {@code X509Certificate} chain for the requested
+ * alias, or null if no there is no result.
+ */
+ public static X509Certificate[] getCertificateChain(Context context, String alias)
+ throws InterruptedException, RemoteException {
+ if (alias == null) {
+ throw new NullPointerException("alias == null");
+ }
+ KeyChainConnection keyChainConnection = bind(context);
+ try {
+ String authToken = authToken(context, alias);
+ if (authToken == null) {
+ return null;
+ }
+ IKeyChainService keyChainService = keyChainConnection.getService();
byte[] certificateBytes = keyChainService.getCertificate(alias, authToken);
- return new KeyChainResult(toPrivateKey(privateKeyBytes),
- toCertificate(certificateBytes));
+ return new X509Certificate[] { toCertificate(certificateBytes) };
} finally {
keyChainConnection.close();
}
@@ -146,6 +207,50 @@
}
}
+ private static String authToken(Context context, String alias) {
+ AccountManager accountManager = AccountManager.get(context);
+ AccountManagerFuture<Bundle> future = accountManager.getAuthToken(getAccount(context),
+ alias,
+ false,
+ null,
+ null);
+ Bundle bundle;
+ try {
+ bundle = future.getResult();
+ } catch (OperationCanceledException e) {
+ throw new AssertionError(e);
+ } catch (IOException e) {
+ // KeyChainAccountAuthenticator doesn't do I/O
+ throw new AssertionError(e);
+ } catch (AuthenticatorException e) {
+ throw new AssertionError(e);
+ }
+ Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT);
+ if (intent != null) {
+ return null;
+ }
+ String authToken = bundle.getString(AccountManager.KEY_AUTHTOKEN);
+ if (authToken == null) {
+ throw new AssertionError("Invalid authtoken");
+ }
+ return authToken;
+ }
+
+ private static Account getAccount(Context context) {
+ AccountManager accountManager = AccountManager.get(context);
+ Account[] accounts = accountManager.getAccountsByType(ACCOUNT_TYPE);
+ if (accounts.length == 0) {
+ try {
+ // Account is created if necessary during binding of the IKeyChainService
+ bind(context).close();
+ } catch (InterruptedException e) {
+ throw new AssertionError(e);
+ }
+ accounts = accountManager.getAccountsByType(ACCOUNT_TYPE);
+ }
+ return accounts[0];
+ }
+
/**
* @hide for reuse by CertInstaller and Settings.
* @see KeyChain#bind
diff --git a/keystore/java/android/security/KeyChainAliasResponse.java b/keystore/java/android/security/KeyChainAliasResponse.java
new file mode 100644
index 0000000..bcca123
--- /dev/null
+++ b/keystore/java/android/security/KeyChainAliasResponse.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.security;
+
+import android.content.Intent;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+
+/**
+ * The KeyChainAliasResponse is the callback for {@link
+ * KeyChain#chooseAlias}.
+ *
+ * @hide
+ */
+public interface KeyChainAliasResponse {
+
+ /**
+ * Called with the alias of the certificate chosen by the user, or
+ * null if no value was chosen.
+ */
+ public void alias(String alias);
+}
diff --git a/keystore/java/android/security/KeyChainResult.java b/keystore/java/android/security/KeyChainResult.java
deleted file mode 100644
index 85a2921..0000000
--- a/keystore/java/android/security/KeyChainResult.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.security;
-
-import android.content.Intent;
-import java.security.PrivateKey;
-import java.security.cert.X509Certificate;
-
-/**
- * The KeyChainResult is the complex result value from {@link
- * KeyChain#get}. The caller should first inspect {@link #getIntent}
- * to determine if the user needs to grant the application access to
- * the protected contents. If {@code getIntent} returns null, access
- * has been granted and the methods {@link #getPrivateKey} and {@link
- * #getCertificate} can be used to access the credentials.
- *
- * @hide
- */
-public final class KeyChainResult {
-
- private final Intent intent;
- private final PrivateKey privateKey;
- private final X509Certificate certificate;
-
- KeyChainResult(Intent intent) {
- this(intent, null, null);
- }
-
- KeyChainResult(PrivateKey privateKey, X509Certificate certificate) {
- this(null, privateKey, certificate);
- }
-
- private KeyChainResult(Intent intent, PrivateKey privateKey, X509Certificate certificate) {
- this.intent = intent;
- this.privateKey = privateKey;
- this.certificate = certificate;
- }
-
- public Intent getIntent() {
- return intent;
- }
-
- public PrivateKey getPrivateKey() {
- checkIntent();
- return privateKey;
- }
-
- public X509Certificate getCertificate() {
- checkIntent();
- return certificate;
- }
-
- private void checkIntent() {
- if (intent != null) {
- throw new IllegalStateException("non-null Intent, check getIntent()");
- }
- }
-
-}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index e1daede..253010c 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -828,29 +828,64 @@
* or {@link #SCO_AUDIO_STATE_CONNECTED}
*
* @see #startBluetoothSco()
+ * @deprecated Use {@link #ACTION_SCO_AUDIO_STATE_UPDATED} instead
*/
+ @Deprecated
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_SCO_AUDIO_STATE_CHANGED =
"android.media.SCO_AUDIO_STATE_CHANGED";
+
+ /**
+ * Sticky broadcast intent action indicating that the bluetoooth SCO audio
+ * connection state has been updated.
+ * <p>This intent has two extras:
+ * <ul>
+ * <li> {@link #EXTRA_SCO_AUDIO_STATE} - The new SCO audio state. </li>
+ * <li> {@link #EXTRA_SCO_AUDIO_PREVIOUS_STATE}- The previous SCO audio state. </li>
+ * </ul>
+ * <p> EXTRA_SCO_AUDIO_STATE or EXTRA_SCO_AUDIO_PREVIOUS_STATE can be any of:
+ * <ul>
+ * <li> {@link #SCO_AUDIO_STATE_DISCONNECTED}, </li>
+ * <li> {@link #SCO_AUDIO_STATE_CONNECTING} or </li>
+ * <li> {@link #SCO_AUDIO_STATE_CONNECTED}, </li>
+ * </ul>
+ * @see #startBluetoothSco()
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_SCO_AUDIO_STATE_UPDATED =
+ "android.media.ACTION_SCO_AUDIO_STATE_UPDATED";
+
/**
- * Extra for intent {@link #ACTION_SCO_AUDIO_STATE_CHANGED} containing the new
- * bluetooth SCO connection state.
+ * Extra for intent {@link #ACTION_SCO_AUDIO_STATE_CHANGED} or
+ * {@link #ACTION_SCO_AUDIO_STATE_UPDATED} containing the new bluetooth SCO connection state.
*/
public static final String EXTRA_SCO_AUDIO_STATE =
"android.media.extra.SCO_AUDIO_STATE";
/**
- * Value for extra {@link #EXTRA_SCO_AUDIO_STATE} indicating that the
- * SCO audio channel is not established
+ * Extra for intent {@link #ACTION_SCO_AUDIO_STATE_UPDATED} containing the previous
+ * bluetooth SCO connection state.
+ */
+ public static final String EXTRA_SCO_AUDIO_PREVIOUS_STATE =
+ "android.media.extra.SCO_AUDIO_PREVIOUS_STATE";
+
+ /**
+ * Value for extra EXTRA_SCO_AUDIO_STATE or EXTRA_SCO_AUDIO_PREVIOUS_STATE
+ * indicating that the SCO audio channel is not established
*/
public static final int SCO_AUDIO_STATE_DISCONNECTED = 0;
/**
- * Value for extra {@link #EXTRA_SCO_AUDIO_STATE} indicating that the
- * SCO audio channel is established
+ * Value for extra {@link #EXTRA_SCO_AUDIO_STATE} or {@link #EXTRA_SCO_AUDIO_PREVIOUS_STATE}
+ * indicating that the SCO audio channel is established
*/
public static final int SCO_AUDIO_STATE_CONNECTED = 1;
/**
- * Value for extra {@link #EXTRA_SCO_AUDIO_STATE} indicating that
+ * Value for extra EXTRA_SCO_AUDIO_STATE or EXTRA_SCO_AUDIO_PREVIOUS_STATE
+ * indicating that the SCO audio channel is being established
+ */
+ public static final int SCO_AUDIO_STATE_CONNECTING = 2;
+ /**
+ * Value for extra EXTRA_SCO_AUDIO_STATE indicating that
* there was an error trying to obtain the state
*/
public static final int SCO_AUDIO_STATE_ERROR = -1;
@@ -878,29 +913,37 @@
* to/from a bluetooth SCO headset while the phone is not in call.
* <p>As the SCO connection establishment can take several seconds,
* applications should not rely on the connection to be available when the method
- * returns but instead register to receive the intent {@link #ACTION_SCO_AUDIO_STATE_CHANGED}
+ * returns but instead register to receive the intent {@link #ACTION_SCO_AUDIO_STATE_UPDATED}
* and wait for the state to be {@link #SCO_AUDIO_STATE_CONNECTED}.
- * <p>As the connection is not guaranteed to succeed, applications must wait for this intent with
- * a timeout.
- * <p>When finished with the SCO connection or if the establishment times out,
- * the application must call {@link #stopBluetoothSco()} to clear the request and turn
- * down the bluetooth connection.
+ * <p>As the ACTION_SCO_AUDIO_STATE_UPDATED intent is sticky, the application can check the SCO
+ * audio state before calling startBluetoothSco() by reading the intent returned by the receiver
+ * registration. If the state is already CONNECTED, no state change will be received via the
+ * intent after calling startBluetoothSco(). It is however useful to call startBluetoothSco()
+ * so that the connection stays active in case the current initiator stops the connection.
+ * <p>Unless the connection is already active as described above, the state will always
+ * transition from DISCONNECTED to CONNECTING and then either to CONNECTED if the connection
+ * succeeds or back to DISCONNECTED if the connection fails (e.g no headset is connected).
+ * <p>When finished with the SCO connection or if the establishment fails, the application must
+ * call {@link #stopBluetoothSco()} to clear the request and turn down the bluetooth connection.
* <p>Even if a SCO connection is established, the following restrictions apply on audio
* output streams so that they can be routed to SCO headset:
- * - the stream type must be {@link #STREAM_VOICE_CALL}
- * - the format must be mono
- * - the sampling must be 16kHz or 8kHz
+ * <ul>
+ * <li> the stream type must be {@link #STREAM_VOICE_CALL} </li>
+ * <li> the format must be mono </li>
+ * <li> the sampling must be 16kHz or 8kHz </li>
+ * </ul>
* <p>The following restrictions apply on input streams:
- * - the format must be mono
- * - the sampling must be 8kHz
- *
+ * <ul>
+ * <li> the format must be mono </li>
+ * <li> the sampling must be 8kHz </li>
+ * </ul>
* <p>Note that the phone application always has the priority on the usage of the SCO
* connection for telephony. If this method is called while the phone is in call
* it will be ignored. Similarly, if a call is received or sent while an application
* is using the SCO connection, the connection will be lost for the application and NOT
* returned automatically when the call ends.
* @see #stopBluetoothSco()
- * @see #ACTION_SCO_AUDIO_STATE_CHANGED
+ * @see #ACTION_SCO_AUDIO_STATE_UPDATED
*/
public void startBluetoothSco(){
IAudioService service = getService();
@@ -917,7 +960,7 @@
* {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS}.
* <p>This method must be called by applications having requested the use of
* bluetooth SCO audio with {@link #startBluetoothSco()}
- * when finished with the SCO connection or if the establishment times out.
+ * when finished with the SCO connection or if connection fails.
* @see #startBluetoothSco()
*/
public void stopBluetoothSco(){
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 504cfde..4e77fcb 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -112,9 +112,12 @@
private static final int MSG_LOAD_SOUND_EFFECTS = 9;
private static final int MSG_SET_FORCE_USE = 10;
private static final int MSG_PERSIST_MEDIABUTTONRECEIVER = 11;
-
+ private static final int MSG_BT_HEADSET_CNCT_FAILED = 12;
private static final int BTA2DP_DOCK_TIMEOUT_MILLIS = 8000;
+ // Timeout for connection to bluetooth headset service
+ private static final int BT_HEADSET_CNCT_TIMEOUT_MS = 3000;
+
/** @see AudioSystemThread */
private AudioSystemThread mAudioSystemThread;
@@ -273,11 +276,22 @@
private int mScoAudioState;
// SCO audio state is not active
private static final int SCO_STATE_INACTIVE = 0;
+ // SCO audio activation request waiting for headset service to connect
+ private static final int SCO_STATE_ACTIVATE_REQ = 1;
// SCO audio state is active or starting due to a local request to start a virtual call
- private static final int SCO_STATE_ACTIVE_INTERNAL = 1;
+ private static final int SCO_STATE_ACTIVE_INTERNAL = 3;
+ // SCO audio deactivation request waiting for headset service to connect
+ private static final int SCO_STATE_DEACTIVATE_REQ = 5;
+
// SCO audio state is active due to an action in BT handsfree (either voice recognition or
// in call audio)
private static final int SCO_STATE_ACTIVE_EXTERNAL = 2;
+ // Deactivation request for all SCO connections (initiated by audio mode change)
+ // waiting for headset service to connect
+ private static final int SCO_STATE_DEACTIVATE_EXT_REQ = 4;
+
+ // Current connection state indicated by bluetooth headset
+ private int mScoConnectionState;
// true if boot sequence has been completed
private boolean mBootCompleted;
@@ -335,13 +349,6 @@
AudioSystem.setErrorCallback(mAudioSystemCallback);
- mBluetoothHeadsetDevice = null;
- BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
- if (adapter != null) {
- adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener,
- BluetoothProfile.HEADSET);
- }
-
// Register for device connection intent broadcasts.
IntentFilter intentFilter =
new IntentFilter(Intent.ACTION_HEADSET_PLUG);
@@ -768,17 +775,7 @@
if (AudioSystem.setPhoneState(mode) == AudioSystem.AUDIO_STATUS_OK) {
AudioService.this.mMode = mode;
if (mode != AudioSystem.MODE_NORMAL) {
- synchronized(mScoClients) {
- checkScoAudioState();
- if (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL) {
- mBluetoothHeadset.stopVoiceRecognition(
- mBluetoothHeadsetDevice);
- mBluetoothHeadset.stopScoUsingVirtualVoiceCall(
- mBluetoothHeadsetDevice);
- } else {
- clearAllScoClients(mCb, true);
- }
- }
+ disconnectBluetoothSco(mCb);
}
}
}
@@ -856,16 +853,7 @@
// when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
// SCO connections not started by the application changing the mode
if (mode != AudioSystem.MODE_NORMAL) {
- synchronized(mScoClients) {
- checkScoAudioState();
- if (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL) {
- mBluetoothHeadset.stopVoiceRecognition(mBluetoothHeadsetDevice);
- mBluetoothHeadset.stopScoUsingVirtualVoiceCall(
- mBluetoothHeadsetDevice);
- } else {
- clearAllScoClients(cb, true);
- }
- }
+ disconnectBluetoothSco(cb);
}
}
}
@@ -1213,7 +1201,8 @@
/** @see AudioManager#startBluetoothSco() */
public void startBluetoothSco(IBinder cb){
- if (!checkAudioSettingsPermission("startBluetoothSco()")) {
+ if (!checkAudioSettingsPermission("startBluetoothSco()") ||
+ !mBootCompleted) {
return;
}
ScoClient client = getScoClient(cb, true);
@@ -1222,7 +1211,8 @@
/** @see AudioManager#stopBluetoothSco() */
public void stopBluetoothSco(IBinder cb){
- if (!checkAudioSettingsPermission("stopBluetoothSco()")) {
+ if (!checkAudioSettingsPermission("stopBluetoothSco()") ||
+ !mBootCompleted) {
return;
}
ScoClient client = getScoClient(cb, false);
@@ -1322,25 +1312,57 @@
}
private void requestScoState(int state) {
- if (mBluetoothHeadset == null) {
- return;
- }
-
checkScoAudioState();
-
- if (totalCount() == 0 &&
- mBluetoothHeadsetDevice != null) {
- // Accept SCO audio activation only in NORMAL audio mode or if the mode is
- // currently controlled by the same client.
- if ((AudioService.this.mMode == AudioSystem.MODE_NORMAL ||
- mSetModeDeathHandlers.get(0).getBinder() == mCb) &&
- state == BluetoothHeadset.STATE_AUDIO_CONNECTED &&
- mScoAudioState == SCO_STATE_INACTIVE) {
- mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
- mBluetoothHeadset.startScoUsingVirtualVoiceCall(mBluetoothHeadsetDevice);
+ if (totalCount() == 0) {
+ if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
+ // Make sure that the state transitions to CONNECTING even if we cannot initiate
+ // the connection.
+ broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTING);
+ // Accept SCO audio activation only in NORMAL audio mode or if the mode is
+ // currently controlled by the same client.
+ if ((AudioService.this.mMode == AudioSystem.MODE_NORMAL ||
+ mSetModeDeathHandlers.get(0).getBinder() == mCb) &&
+ mBluetoothHeadsetDevice != null &&
+ (mScoAudioState == SCO_STATE_INACTIVE ||
+ mScoAudioState == SCO_STATE_DEACTIVATE_REQ)) {
+ if (mScoAudioState == SCO_STATE_INACTIVE) {
+ if (mBluetoothHeadset != null) {
+ if (mBluetoothHeadset.startScoUsingVirtualVoiceCall(
+ mBluetoothHeadsetDevice)) {
+ mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
+ } else {
+ broadcastScoConnectionState(
+ AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ }
+ } else if (getBluetoothHeadset()) {
+ mScoAudioState = SCO_STATE_ACTIVATE_REQ;
+ }
+ } else {
+ mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
+ broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTED);
+ }
+ } else {
+ broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ }
} else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED &&
- mScoAudioState == SCO_STATE_ACTIVE_INTERNAL){
- mBluetoothHeadset.stopScoUsingVirtualVoiceCall(mBluetoothHeadsetDevice);
+ mBluetoothHeadsetDevice != null &&
+ (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL ||
+ mScoAudioState == SCO_STATE_ACTIVATE_REQ)) {
+ if (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL) {
+ if (mBluetoothHeadset != null) {
+ if (!mBluetoothHeadset.stopScoUsingVirtualVoiceCall(
+ mBluetoothHeadsetDevice)) {
+ mScoAudioState = SCO_STATE_INACTIVE;
+ broadcastScoConnectionState(
+ AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ }
+ } else if (getBluetoothHeadset()) {
+ mScoAudioState = SCO_STATE_DEACTIVATE_REQ;
+ }
+ } else {
+ mScoAudioState = SCO_STATE_INACTIVE;
+ broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ }
}
}
}
@@ -1348,7 +1370,7 @@
private void checkScoAudioState() {
if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null &&
- mScoAudioState != SCO_STATE_ACTIVE_INTERNAL &&
+ mScoAudioState == SCO_STATE_INACTIVE &&
mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
!= BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
@@ -1391,10 +1413,72 @@
}
}
+ private boolean getBluetoothHeadset() {
+ boolean result = false;
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ if (adapter != null) {
+ result = adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener,
+ BluetoothProfile.HEADSET);
+ }
+ // If we could not get a bluetooth headset proxy, send a failure message
+ // without delay to reset the SCO audio state and clear SCO clients.
+ // If we could get a proxy, send a delayed failure message that will reset our state
+ // in case we don't receive onServiceConnected().
+ sendMsg(mAudioHandler, MSG_BT_HEADSET_CNCT_FAILED, 0,
+ SENDMSG_REPLACE, 0, 0, null, result ? BT_HEADSET_CNCT_TIMEOUT_MS : 0);
+ return result;
+ }
+
+ private void disconnectBluetoothSco(IBinder exceptBinder) {
+ synchronized(mScoClients) {
+ checkScoAudioState();
+ if (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL ||
+ mScoAudioState == SCO_STATE_DEACTIVATE_EXT_REQ) {
+ if (mBluetoothHeadsetDevice != null) {
+ if (mBluetoothHeadset != null) {
+ if (!mBluetoothHeadset.stopVoiceRecognition(
+ mBluetoothHeadsetDevice) ||
+ !mBluetoothHeadset.stopScoUsingVirtualVoiceCall(
+ mBluetoothHeadsetDevice)) {
+ sendMsg(mAudioHandler, MSG_BT_HEADSET_CNCT_FAILED, 0,
+ SENDMSG_REPLACE, 0, 0, null, 0);
+ }
+ } else if (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL &&
+ getBluetoothHeadset()) {
+ mScoAudioState = SCO_STATE_DEACTIVATE_EXT_REQ;
+ }
+ }
+ } else {
+ clearAllScoClients(exceptBinder, true);
+ }
+ }
+ }
+
+ private void resetBluetoothSco() {
+ synchronized(mScoClients) {
+ clearAllScoClients(null, false);
+ mScoAudioState = SCO_STATE_INACTIVE;
+ broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ }
+ }
+
+ private void broadcastScoConnectionState(int state) {
+ if (state != mScoConnectionState) {
+ Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);
+ newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, state);
+ newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_PREVIOUS_STATE,
+ mScoConnectionState);
+ mContext.sendStickyBroadcast(newIntent);
+ mScoConnectionState = state;
+ }
+ }
+
private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
new BluetoothProfile.ServiceListener() {
public void onServiceConnected(int profile, BluetoothProfile proxy) {
synchronized (mScoClients) {
+ // Discard timeout message
+ mAudioHandler.removeMessages(MSG_BT_HEADSET_CNCT_FAILED);
mBluetoothHeadset = (BluetoothHeadset) proxy;
List<BluetoothDevice> deviceList = mBluetoothHeadset.getConnectedDevices();
if (deviceList.size() > 0) {
@@ -1402,19 +1486,41 @@
} else {
mBluetoothHeadsetDevice = null;
}
+ // Refresh SCO audio state
+ checkScoAudioState();
+ // Continue pending action if any
+ if (mScoAudioState == SCO_STATE_ACTIVATE_REQ ||
+ mScoAudioState == SCO_STATE_DEACTIVATE_REQ ||
+ mScoAudioState == SCO_STATE_DEACTIVATE_EXT_REQ) {
+ boolean status = false;
+ if (mBluetoothHeadsetDevice != null) {
+ switch (mScoAudioState) {
+ case SCO_STATE_ACTIVATE_REQ:
+ mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
+ status = mBluetoothHeadset.startScoUsingVirtualVoiceCall(
+ mBluetoothHeadsetDevice);
+ break;
+ case SCO_STATE_DEACTIVATE_REQ:
+ status = mBluetoothHeadset.stopScoUsingVirtualVoiceCall(
+ mBluetoothHeadsetDevice);
+ break;
+ case SCO_STATE_DEACTIVATE_EXT_REQ:
+ status = mBluetoothHeadset.stopVoiceRecognition(
+ mBluetoothHeadsetDevice) &&
+ mBluetoothHeadset.stopScoUsingVirtualVoiceCall(
+ mBluetoothHeadsetDevice);
+ }
+ }
+ if (!status) {
+ sendMsg(mAudioHandler, MSG_BT_HEADSET_CNCT_FAILED, 0,
+ SENDMSG_REPLACE, 0, 0, null, 0);
+ }
+ }
}
}
public void onServiceDisconnected(int profile) {
synchronized (mScoClients) {
- if (mBluetoothHeadset != null) {
- List<BluetoothDevice> devices = mBluetoothHeadset.getConnectedDevices();
- if (devices.size() == 0) {
- mBluetoothHeadsetDevice = null;
- clearAllScoClients(null, false);
- mScoAudioState = SCO_STATE_INACTIVE;
- }
- mBluetoothHeadset = null;
- }
+ mBluetoothHeadset = null;
}
}
};
@@ -2041,6 +2147,10 @@
case MSG_PERSIST_MEDIABUTTONRECEIVER:
persistMediaButtonReceiver( (ComponentName) msg.obj );
break;
+
+ case MSG_BT_HEADSET_CNCT_FAILED:
+ resetBluetoothSco();
+ break;
}
}
}
@@ -2241,8 +2351,7 @@
address);
mConnectedDevices.remove(device);
mBluetoothHeadsetDevice = null;
- clearAllScoClients(null, false);
- mScoAudioState = SCO_STATE_INACTIVE;
+ resetBluetoothSco();
} else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
AudioSystem.setDeviceConnectionState(device,
AudioSystem.DEVICE_STATE_AVAILABLE,
@@ -2332,26 +2441,34 @@
}
} else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
boolean broadcast = false;
- int audioState = AudioManager.SCO_AUDIO_STATE_ERROR;
+ int state = AudioManager.SCO_AUDIO_STATE_ERROR;
synchronized (mScoClients) {
int btState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
- if (!mScoClients.isEmpty() && mScoAudioState == SCO_STATE_ACTIVE_INTERNAL) {
+ // broadcast intent if the connection was initated by AudioService
+ if (!mScoClients.isEmpty() &&
+ (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL ||
+ mScoAudioState == SCO_STATE_ACTIVATE_REQ ||
+ mScoAudioState == SCO_STATE_DEACTIVATE_REQ)) {
broadcast = true;
}
switch (btState) {
case BluetoothHeadset.STATE_AUDIO_CONNECTED:
- audioState = AudioManager.SCO_AUDIO_STATE_CONNECTED;
- if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL) {
+ state = AudioManager.SCO_AUDIO_STATE_CONNECTED;
+ if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL &&
+ mScoAudioState != SCO_STATE_DEACTIVATE_REQ &&
+ mScoAudioState != SCO_STATE_DEACTIVATE_EXT_REQ) {
mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
}
break;
case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
- audioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED;
+ state = AudioManager.SCO_AUDIO_STATE_DISCONNECTED;
mScoAudioState = SCO_STATE_INACTIVE;
clearAllScoClients(null, false);
break;
case BluetoothHeadset.STATE_AUDIO_CONNECTING:
- if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL) {
+ if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL &&
+ mScoAudioState != SCO_STATE_DEACTIVATE_REQ &&
+ mScoAudioState != SCO_STATE_DEACTIVATE_EXT_REQ) {
mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
}
default:
@@ -2361,14 +2478,23 @@
}
}
if (broadcast) {
+ broadcastScoConnectionState(state);
+ //FIXME: this is to maintain compatibility with deprecated intent
+ // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate.
Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED);
- newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, audioState);
+ newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, state);
mContext.sendStickyBroadcast(newIntent);
}
} else if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
mBootCompleted = true;
sendMsg(mAudioHandler, MSG_LOAD_SOUND_EFFECTS, SHARED_MSG, SENDMSG_NOOP,
0, 0, null, 0);
+
+ mScoConnectionState = AudioManager.SCO_AUDIO_STATE_ERROR;
+ resetBluetoothSco();
+ getBluetoothHeadset();
+ //FIXME: this is to maintain compatibility with deprecated intent
+ // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate.
Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED);
newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE,
AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp
index fb7a871..70053ea 100644
--- a/media/libstagefright/AwesomePlayer.cpp
+++ b/media/libstagefright/AwesomePlayer.cpp
@@ -400,6 +400,7 @@
}
void AwesomePlayer::reset() {
+ LOGI("reset");
Mutex::Autolock autoLock(mLock);
reset_l();
}
@@ -413,8 +414,10 @@
Playback::STOP, 0);
mDecryptHandle = NULL;
mDrmManagerClient = NULL;
+ LOGI("DRM manager client stopped");
}
+
if (mFlags & PLAYING) {
uint32_t params = IMediaPlayerService::kBatteryDataTrackDecoder;
if ((mAudioSource != NULL) && (mAudioSource != mAudioTrack)) {
@@ -447,6 +450,7 @@
mPreparedCondition.wait(mLock);
}
+ LOGI("cancel player events");
cancelPlayerEvents();
mWVMExtractor.clear();
@@ -1081,6 +1085,7 @@
usleep(1000);
}
IPCThreadState::self()->flushCommands();
+ LOGI("video decoder shutdown completed");
}
void AwesomePlayer::setNativeWindow_l(const sp<ANativeWindow> &native) {
diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp
index 0f0ffd4..ba495cc 100644
--- a/media/libstagefright/OMXCodec.cpp
+++ b/media/libstagefright/OMXCodec.cpp
@@ -3443,7 +3443,7 @@
}
status_t OMXCodec::stop() {
- CODEC_LOGV("stop mState=%d", mState);
+ CODEC_LOGI("stop mState=%d", mState);
Mutex::Autolock autoLock(mLock);
@@ -3505,6 +3505,7 @@
mLeftOverBuffer = NULL;
}
+ CODEC_LOGI("stopping video source");
mSource->stop();
CODEC_LOGI("stopped in state %d", mState);
diff --git a/media/libstagefright/WVMExtractor.cpp b/media/libstagefright/WVMExtractor.cpp
index 83a1eaa..26eda0c 100644
--- a/media/libstagefright/WVMExtractor.cpp
+++ b/media/libstagefright/WVMExtractor.cpp
@@ -45,8 +45,7 @@
static Mutex gWVMutex;
WVMExtractor::WVMExtractor(const sp<DataSource> &source)
- : mDataSource(source),
- mUseAdaptiveStreaming(false) {
+ : mDataSource(source) {
{
Mutex::Autolock autoLock(gWVMutex);
if (gVendorLibHandle == NULL) {
@@ -59,13 +58,12 @@
}
}
- typedef MediaExtractor *(*GetInstanceFunc)(sp<DataSource>);
+ typedef WVMLoadableExtractor *(*GetInstanceFunc)(sp<DataSource>);
GetInstanceFunc getInstanceFunc =
(GetInstanceFunc) dlsym(gVendorLibHandle,
"_ZN7android11GetInstanceENS_2spINS_10DataSourceEEE");
if (getInstanceFunc) {
- LOGD("Calling GetInstanceFunc");
mImpl = (*getInstanceFunc)(source);
CHECK(mImpl != NULL);
} else {
@@ -102,19 +100,17 @@
}
int64_t WVMExtractor::getCachedDurationUs(status_t *finalStatus) {
- // TODO: Fill this with life.
+ if (mImpl == NULL) {
+ return 0;
+ }
- *finalStatus = OK;
-
- return 0;
+ return mImpl->getCachedDurationUs(finalStatus);
}
void WVMExtractor::setAdaptiveStreamingMode(bool adaptive) {
- mUseAdaptiveStreaming = adaptive;
-}
-
-bool WVMExtractor::getAdaptiveStreamingMode() const {
- return mUseAdaptiveStreaming;
+ if (mImpl != NULL) {
+ mImpl->setAdaptiveStreamingMode(adaptive);
+ }
}
} //namespace android
diff --git a/media/libstagefright/codecs/m4v_h263/dec/SoftMPEG4.cpp b/media/libstagefright/codecs/m4v_h263/dec/SoftMPEG4.cpp
index 13e1662..cffbfb5 100644
--- a/media/libstagefright/codecs/m4v_h263/dec/SoftMPEG4.cpp
+++ b/media/libstagefright/codecs/m4v_h263/dec/SoftMPEG4.cpp
@@ -360,10 +360,14 @@
mFramesConfigured = true;
}
- uint32_t timestamp = 0xFFFFFFFF;
+ uint32_t useExtTimestamp = (inHeader->nOffset == 0);
+
+ // decoder deals in ms, OMX in us.
+ uint32_t timestamp =
+ useExtTimestamp ? (inHeader->nTimeStamp + 500) / 1000 : 0xFFFFFFFF;
+
int32_t bufferSize = inHeader->nFilledLen;
- uint32_t useExtTimestamp = 0;
if (PVDecodeVideoFrame(
mHandle, &bitstream, ×tamp, &bufferSize,
&useExtTimestamp,
@@ -379,13 +383,20 @@
return;
}
- outHeader->nTimeStamp = inHeader->nTimeStamp;
+ // decoder deals in ms, OMX in us.
+ outHeader->nTimeStamp = timestamp * 1000;
- inInfo->mOwnedByUs = false;
- inQueue.erase(inQueue.begin());
- inInfo = NULL;
- notifyEmptyBufferDone(inHeader);
- inHeader = NULL;
+ CHECK_LE(bufferSize, inHeader->nFilledLen);
+ inHeader->nOffset += inHeader->nFilledLen - bufferSize;
+ inHeader->nFilledLen = bufferSize;
+
+ if (inHeader->nFilledLen == 0) {
+ inInfo->mOwnedByUs = false;
+ inQueue.erase(inQueue.begin());
+ inInfo = NULL;
+ notifyEmptyBufferDone(inHeader);
+ inHeader = NULL;
+ }
++mInputBufferCount;
diff --git a/media/libstagefright/include/SimpleSoftOMXComponent.h b/media/libstagefright/include/SimpleSoftOMXComponent.h
index 2a29a7d..50cd275 100644
--- a/media/libstagefright/include/SimpleSoftOMXComponent.h
+++ b/media/libstagefright/include/SimpleSoftOMXComponent.h
@@ -36,7 +36,7 @@
OMX_PTR appData,
OMX_COMPONENTTYPE **component);
- virtual ~SimpleSoftOMXComponent();
+ virtual void prepareForDestruction();
void onMessageReceived(const sp<AMessage> &msg);
diff --git a/media/libstagefright/include/SoftOMXComponent.h b/media/libstagefright/include/SoftOMXComponent.h
index 053bc22..a808611 100644
--- a/media/libstagefright/include/SoftOMXComponent.h
+++ b/media/libstagefright/include/SoftOMXComponent.h
@@ -38,6 +38,8 @@
void setLibHandle(void *libHandle);
void *libHandle() const;
+ virtual void prepareForDestruction() {}
+
protected:
virtual ~SoftOMXComponent();
diff --git a/media/libstagefright/include/WVMExtractor.h b/media/libstagefright/include/WVMExtractor.h
index 62e5aa5..deecd25 100644
--- a/media/libstagefright/include/WVMExtractor.h
+++ b/media/libstagefright/include/WVMExtractor.h
@@ -25,6 +25,15 @@
class DataSource;
+class WVMLoadableExtractor : public MediaExtractor {
+public:
+ WVMLoadableExtractor() {}
+ virtual ~WVMLoadableExtractor() {}
+
+ virtual int64_t getCachedDurationUs(status_t *finalStatus) = 0;
+ virtual void setAdaptiveStreamingMode(bool adaptive) = 0;
+};
+
class WVMExtractor : public MediaExtractor {
public:
WVMExtractor(const sp<DataSource> &source);
@@ -49,20 +58,15 @@
// is used.
void setAdaptiveStreamingMode(bool adaptive);
- // Retrieve the adaptive streaming mode used by the WV component.
- bool getAdaptiveStreamingMode() const;
-
protected:
virtual ~WVMExtractor();
private:
sp<DataSource> mDataSource;
- sp<MediaExtractor> mImpl;
- bool mUseAdaptiveStreaming;
+ sp<WVMLoadableExtractor> mImpl;
WVMExtractor(const WVMExtractor &);
WVMExtractor &operator=(const WVMExtractor &);
-
};
} // namespace android
diff --git a/media/libstagefright/omx/SimpleSoftOMXComponent.cpp b/media/libstagefright/omx/SimpleSoftOMXComponent.cpp
index 179b2a0..f7330f3 100644
--- a/media/libstagefright/omx/SimpleSoftOMXComponent.cpp
+++ b/media/libstagefright/omx/SimpleSoftOMXComponent.cpp
@@ -45,7 +45,11 @@
PRIORITY_AUDIO);
}
-SimpleSoftOMXComponent::~SimpleSoftOMXComponent() {
+void SimpleSoftOMXComponent::prepareForDestruction() {
+ // The looper's queue may still contain messages referencing this
+ // object. Make sure those are flushed before returning so that
+ // a subsequent dlunload() does not pull out the rug from under us.
+
mLooper->unregisterHandler(mHandler->id());
mLooper->stop();
}
diff --git a/media/libstagefright/omx/SoftOMXPlugin.cpp b/media/libstagefright/omx/SoftOMXPlugin.cpp
index 6bd6624..04ca39e 100644
--- a/media/libstagefright/omx/SoftOMXPlugin.cpp
+++ b/media/libstagefright/omx/SoftOMXPlugin.cpp
@@ -21,6 +21,7 @@
#include "SoftOMXPlugin.h"
#include "include/SoftOMXComponent.h"
+#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AString.h>
#include <dlfcn.h>
@@ -126,8 +127,11 @@
(SoftOMXComponent *)
((OMX_COMPONENTTYPE *)component)->pComponentPrivate;
+ me->prepareForDestruction();
+
void *libHandle = me->libHandle();
+ CHECK_EQ(me->getStrongCount(), 1);
me->decStrong(this);
me = NULL;
diff --git a/native/include/android/configuration.h b/native/include/android/configuration.h
index 91533c8..39fef21 100644
--- a/native/include/android/configuration.h
+++ b/native/include/android/configuration.h
@@ -77,6 +77,7 @@
ACONFIGURATION_UI_MODE_TYPE_NORMAL = 0x01,
ACONFIGURATION_UI_MODE_TYPE_DESK = 0x02,
ACONFIGURATION_UI_MODE_TYPE_CAR = 0x03,
+ ACONFIGURATION_UI_MODE_TYPE_TELEVISION = 0x04,
ACONFIGURATION_UI_MODE_NIGHT_ANY = 0x00,
ACONFIGURATION_UI_MODE_NIGHT_NO = 0x1,
diff --git a/services/input/EventHub.cpp b/services/input/EventHub.cpp
index ff4b11a..af30887 100644
--- a/services/input/EventHub.cpp
+++ b/services/input/EventHub.cpp
@@ -101,7 +101,7 @@
const InputDeviceIdentifier& identifier) :
next(NULL),
fd(fd), id(id), path(path), identifier(identifier),
- classes(0), keyBitmask(NULL), relBitmask(NULL),
+ classes(0), keyBitmask(NULL), relBitmask(NULL), propBitmask(NULL),
configuration(NULL), virtualKeyMap(NULL) {
}
@@ -109,6 +109,7 @@
close();
delete[] keyBitmask;
delete[] relBitmask;
+ delete[] propBitmask;
delete configuration;
delete virtualKeyMap;
}
@@ -205,6 +206,18 @@
return false;
}
+bool EventHub::hasInputProperty(int32_t deviceId, int property) const {
+ if (property >= 0 && property <= INPUT_PROP_MAX) {
+ AutoMutex _l(mLock);
+
+ Device* device = getDeviceLocked(deviceId);
+ if (device && device->propBitmask) {
+ return test_bit(property, device->propBitmask);
+ }
+ }
+ return false;
+}
+
int32_t EventHub::getScanCodeState(int32_t deviceId, int32_t scanCode) const {
if (scanCode >= 0 && scanCode <= KEY_MAX) {
AutoMutex _l(mLock);
@@ -834,23 +847,23 @@
memset(sw_bitmask, 0, sizeof(sw_bitmask));
ioctl(fd, EVIOCGBIT(EV_SW, sizeof(sw_bitmask)), sw_bitmask);
+ uint8_t prop_bitmask[sizeof_bit_array(INPUT_PROP_MAX + 1)];
+ memset(prop_bitmask, 0, sizeof(prop_bitmask));
+ ioctl(fd, EVIOCGPROP(sizeof(prop_bitmask)), prop_bitmask);
+
device->keyBitmask = new uint8_t[sizeof(key_bitmask)];
- if (device->keyBitmask != NULL) {
- memcpy(device->keyBitmask, key_bitmask, sizeof(key_bitmask));
- } else {
+ device->relBitmask = new uint8_t[sizeof(rel_bitmask)];
+ device->propBitmask = new uint8_t[sizeof(prop_bitmask)];
+
+ if (!device->keyBitmask || !device->relBitmask || !device->propBitmask) {
delete device;
- LOGE("out of memory allocating key bitmask");
+ LOGE("out of memory allocating bitmasks");
return -1;
}
- device->relBitmask = new uint8_t[sizeof(rel_bitmask)];
- if (device->relBitmask != NULL) {
- memcpy(device->relBitmask, rel_bitmask, sizeof(rel_bitmask));
- } else {
- delete device;
- LOGE("out of memory allocating rel bitmask");
- return -1;
- }
+ memcpy(device->keyBitmask, key_bitmask, sizeof(key_bitmask));
+ memcpy(device->relBitmask, rel_bitmask, sizeof(rel_bitmask));
+ memcpy(device->propBitmask, prop_bitmask, sizeof(prop_bitmask));
// See if this is a keyboard. Ignore everything in the button range except for
// joystick and gamepad buttons which are handled like keyboards for the most part.
diff --git a/services/input/EventHub.h b/services/input/EventHub.h
index 4d26a95..ca33619 100644
--- a/services/input/EventHub.h
+++ b/services/input/EventHub.h
@@ -34,25 +34,38 @@
#include <linux/input.h>
-/* These constants are not defined in linux/input.h but they are part of the multitouch
- * input protocol. */
+/* These constants are not defined in linux/input.h in the version of the kernel
+ * headers currently provided with Bionic. */
-#define ABS_MT_TOUCH_MAJOR 0x30 /* Major axis of touching ellipse */
-#define ABS_MT_TOUCH_MINOR 0x31 /* Minor axis (omit if circular) */
-#define ABS_MT_WIDTH_MAJOR 0x32 /* Major axis of approaching ellipse */
-#define ABS_MT_WIDTH_MINOR 0x33 /* Minor axis (omit if circular) */
-#define ABS_MT_ORIENTATION 0x34 /* Ellipse orientation */
-#define ABS_MT_POSITION_X 0x35 /* Center X ellipse position */
-#define ABS_MT_POSITION_Y 0x36 /* Center Y ellipse position */
-#define ABS_MT_TOOL_TYPE 0x37 /* Type of touching device (finger, pen, ...) */
-#define ABS_MT_BLOB_ID 0x38 /* Group a set of packets as a blob */
-#define ABS_MT_TRACKING_ID 0x39 /* Unique ID of initiated contact */
-#define ABS_MT_PRESSURE 0x3a /* Pressure on contact area */
+#define EVIOCGPROP(len) _IOC(_IOC_READ, 'E', 0x09, len)
-#define MT_TOOL_FINGER 0 /* Identifies a finger */
-#define MT_TOOL_PEN 1 /* Identifies a pen */
+#define INPUT_PROP_POINTER 0x00
+#define INPUT_PROP_DIRECT 0x01
+#define INPUT_PROP_BUTTONPAD 0x02
+#define INPUT_PROP_SEMI_MT 0x03
+#define INPUT_PROP_MAX 0x1f
+#define INPUT_PROP_CNT (INPUT_PROP_MAX + 1)
+
+#define ABS_MT_SLOT 0x2f
+#define ABS_MT_TOUCH_MAJOR 0x30
+#define ABS_MT_TOUCH_MINOR 0x31
+#define ABS_MT_WIDTH_MAJOR 0x32
+#define ABS_MT_WIDTH_MINOR 0x33
+#define ABS_MT_ORIENTATION 0x34
+#define ABS_MT_POSITION_X 0x35
+#define ABS_MT_POSITION_Y 0x36
+#define ABS_MT_TOOL_TYPE 0x37
+#define ABS_MT_BLOB_ID 0x38
+#define ABS_MT_TRACKING_ID 0x39
+#define ABS_MT_PRESSURE 0x3a
+#define ABS_MT_DISTANCE 0x3b
+
+#define MT_TOOL_FINGER 0
+#define MT_TOOL_PEN 1
#define SYN_MT_REPORT 2
+#define SYN_DROPPED 3
+
/* Convenience constants. */
@@ -172,6 +185,8 @@
virtual bool hasRelativeAxis(int32_t deviceId, int axis) const = 0;
+ virtual bool hasInputProperty(int32_t deviceId, int property) const = 0;
+
virtual status_t mapKey(int32_t deviceId, int scancode,
int32_t* outKeycode, uint32_t* outFlags) const = 0;
@@ -236,6 +251,8 @@
virtual bool hasRelativeAxis(int32_t deviceId, int axis) const;
+ virtual bool hasInputProperty(int32_t deviceId, int property) const;
+
virtual status_t mapKey(int32_t deviceId, int scancode,
int32_t* outKeycode, uint32_t* outFlags) const;
@@ -286,6 +303,7 @@
uint32_t classes;
uint8_t* keyBitmask;
uint8_t* relBitmask;
+ uint8_t* propBitmask;
String8 configurationFile;
PropertyMap* configuration;
VirtualKeyMap* virtualKeyMap;
diff --git a/services/input/InputReader.cpp b/services/input/InputReader.cpp
index 6003207..25a2c78 100644
--- a/services/input/InputReader.cpp
+++ b/services/input/InputReader.cpp
@@ -58,6 +58,9 @@
// --- Constants ---
+// Maximum number of slots supported when using the slot-based Multitouch Protocol B.
+static const size_t MAX_SLOTS = 32;
+
// Quiet time between certain gesture transitions.
// Time to allow for all fingers or buttons to settle into a stable state before
// starting a new gesture.
@@ -809,7 +812,8 @@
// --- InputDevice ---
InputDevice::InputDevice(InputReaderContext* context, int32_t id, const String8& name) :
- mContext(context), mId(id), mName(name), mSources(0), mIsExternal(false) {
+ mContext(context), mId(id), mName(name), mSources(0),
+ mIsExternal(false), mDropUntilNextSync(false) {
}
InputDevice::~InputDevice() {
@@ -898,9 +902,26 @@
rawEvent->value, rawEvent->flags);
#endif
- for (size_t i = 0; i < numMappers; i++) {
- InputMapper* mapper = mMappers[i];
- mapper->process(rawEvent);
+ if (mDropUntilNextSync) {
+ if (rawEvent->type == EV_SYN && rawEvent->scanCode == SYN_REPORT) {
+ mDropUntilNextSync = false;
+#if DEBUG_RAW_EVENTS
+ LOGD("Recovered from input event buffer overrun.");
+#endif
+ } else {
+#if DEBUG_RAW_EVENTS
+ LOGD("Dropped input event while waiting for next input sync.");
+#endif
+ }
+ } else if (rawEvent->type == EV_SYN && rawEvent->scanCode == SYN_DROPPED) {
+ LOGI("Detected input event buffer overrun for device %s.", mName.string());
+ mDropUntilNextSync = true;
+ reset();
+ } else {
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ mapper->process(rawEvent);
+ }
}
}
}
@@ -1812,6 +1833,10 @@
info->addMotionRange(mLocked.orientedRanges.orientation);
}
+ if (mLocked.orientedRanges.haveDistance) {
+ info->addMotionRange(mLocked.orientedRanges.distance);
+ }
+
if (mPointerController != NULL) {
float minX, minY, maxX, maxY;
if (mPointerController->getBounds(&minX, &minY, &maxX, &maxY)) {
@@ -1849,6 +1874,7 @@
dump.appendFormat(INDENT4 "PressureScale: %0.3f\n", mLocked.pressureScale);
dump.appendFormat(INDENT4 "SizeScale: %0.3f\n", mLocked.sizeScale);
dump.appendFormat(INDENT4 "OrientationScale: %0.3f\n", mLocked.orientationScale);
+ dump.appendFormat(INDENT4 "DistanceScale: %0.3f\n", mLocked.distanceScale);
dump.appendFormat(INDENT3 "Last Touch:\n");
dump.appendFormat(INDENT4 "Pointer Count: %d\n", mLastTouch.pointerCount);
@@ -1889,6 +1915,7 @@
mLocked.orientedRanges.haveTouchSize = false;
mLocked.orientedRanges.haveToolSize = false;
mLocked.orientedRanges.haveOrientation = false;
+ mLocked.orientedRanges.haveDistance = false;
mPointerGesture.reset();
}
@@ -1947,9 +1974,14 @@
// The device is a cursor device with a touch pad attached.
// By default don't use the touch pad to move the pointer.
mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_PAD;
+ } else if (getEventHub()->hasInputProperty(getDeviceId(), INPUT_PROP_POINTER)) {
+ // The device is a pointing device like a track pad.
+ mParameters.deviceType = Parameters::DEVICE_TYPE_POINTER;
+ } else if (getEventHub()->hasInputProperty(getDeviceId(), INPUT_PROP_DIRECT)) {
+ // The device is a touch screen.
+ mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_SCREEN;
} else {
- // The device is just a touch pad.
- // By default use the touch pad to move the pointer and to perform related gestures.
+ // The device is a touch pad of unknown purpose.
mParameters.deviceType = Parameters::DEVICE_TYPE_POINTER;
}
@@ -2016,6 +2048,9 @@
mRawAxes.toolMajor.clear();
mRawAxes.toolMinor.clear();
mRawAxes.orientation.clear();
+ mRawAxes.distance.clear();
+ mRawAxes.trackingId.clear();
+ mRawAxes.slot.clear();
}
void TouchInputMapper::dumpRawAxes(String8& dump) {
@@ -2028,6 +2063,9 @@
dumpRawAbsoluteAxisInfo(dump, mRawAxes.toolMajor, "ToolMajor");
dumpRawAbsoluteAxisInfo(dump, mRawAxes.toolMinor, "ToolMinor");
dumpRawAbsoluteAxisInfo(dump, mRawAxes.orientation, "Orientation");
+ dumpRawAbsoluteAxisInfo(dump, mRawAxes.distance, "Distance");
+ dumpRawAbsoluteAxisInfo(dump, mRawAxes.trackingId, "TrackingId");
+ dumpRawAbsoluteAxisInfo(dump, mRawAxes.slot, "Slot");
}
bool TouchInputMapper::configureSurfaceLocked() {
@@ -2234,6 +2272,8 @@
}
}
+ mLocked.orientedRanges.haveOrientation = true;
+
mLocked.orientedRanges.orientation.axis = AMOTION_EVENT_AXIS_ORIENTATION;
mLocked.orientedRanges.orientation.source = mTouchSource;
mLocked.orientedRanges.orientation.min = - M_PI_2;
@@ -2241,6 +2281,31 @@
mLocked.orientedRanges.orientation.flat = 0;
mLocked.orientedRanges.orientation.fuzz = 0;
}
+
+ // Distance
+ mLocked.distanceScale = 0;
+ if (mCalibration.distanceCalibration != Calibration::DISTANCE_CALIBRATION_NONE) {
+ if (mCalibration.distanceCalibration
+ == Calibration::DISTANCE_CALIBRATION_SCALED) {
+ if (mCalibration.haveDistanceScale) {
+ mLocked.distanceScale = mCalibration.distanceScale;
+ } else {
+ mLocked.distanceScale = 1.0f;
+ }
+ }
+
+ mLocked.orientedRanges.haveDistance = true;
+
+ mLocked.orientedRanges.distance.axis = AMOTION_EVENT_AXIS_DISTANCE;
+ mLocked.orientedRanges.distance.source = mTouchSource;
+ mLocked.orientedRanges.distance.min =
+ mRawAxes.distance.minValue * mLocked.distanceScale;
+ mLocked.orientedRanges.distance.max =
+ mRawAxes.distance.minValue * mLocked.distanceScale;
+ mLocked.orientedRanges.distance.flat = 0;
+ mLocked.orientedRanges.distance.fuzz =
+ mRawAxes.distance.fuzz * mLocked.distanceScale;
+ }
}
if (orientationChanged || sizeChanged) {
@@ -2518,6 +2583,23 @@
orientationCalibrationString.string());
}
}
+
+ // Distance
+ out.distanceCalibration = Calibration::DISTANCE_CALIBRATION_DEFAULT;
+ String8 distanceCalibrationString;
+ if (in.tryGetProperty(String8("touch.distance.calibration"), distanceCalibrationString)) {
+ if (distanceCalibrationString == "none") {
+ out.distanceCalibration = Calibration::DISTANCE_CALIBRATION_NONE;
+ } else if (distanceCalibrationString == "scaled") {
+ out.distanceCalibration = Calibration::DISTANCE_CALIBRATION_SCALED;
+ } else if (distanceCalibrationString != "default") {
+ LOGW("Invalid value for touch.distance.calibration: '%s'",
+ distanceCalibrationString.string());
+ }
+ }
+
+ out.haveDistanceScale = in.tryGetProperty(String8("touch.distance.scale"),
+ out.distanceScale);
}
void TouchInputMapper::resolveCalibration() {
@@ -2618,6 +2700,20 @@
default:
break;
}
+
+ // Distance
+ switch (mCalibration.distanceCalibration) {
+ case Calibration::DISTANCE_CALIBRATION_DEFAULT:
+ if (mRawAxes.distance.valid) {
+ mCalibration.distanceCalibration = Calibration::DISTANCE_CALIBRATION_SCALED;
+ } else {
+ mCalibration.distanceCalibration = Calibration::DISTANCE_CALIBRATION_NONE;
+ }
+ break;
+
+ default:
+ break;
+ }
}
void TouchInputMapper::dumpCalibration(String8& dump) {
@@ -2740,6 +2836,23 @@
default:
LOG_ASSERT(false);
}
+
+ // Distance
+ switch (mCalibration.distanceCalibration) {
+ case Calibration::DISTANCE_CALIBRATION_NONE:
+ dump.append(INDENT4 "touch.distance.calibration: none\n");
+ break;
+ case Calibration::DISTANCE_CALIBRATION_SCALED:
+ dump.append(INDENT4 "touch.distance.calibration: scaled\n");
+ break;
+ default:
+ LOG_ASSERT(false);
+ }
+
+ if (mCalibration.haveDistanceScale) {
+ dump.appendFormat(INDENT4 "touch.distance.scale: %0.3f\n",
+ mCalibration.distanceScale);
+ }
}
void TouchInputMapper::reset() {
@@ -3247,6 +3360,16 @@
orientation = 0;
}
+ // Distance
+ float distance;
+ switch (mCalibration.distanceCalibration) {
+ case Calibration::DISTANCE_CALIBRATION_SCALED:
+ distance = in.distance * mLocked.distanceScale;
+ break;
+ default:
+ distance = 0;
+ }
+
// X and Y
// Adjust coords for surface orientation.
float x, y;
@@ -3289,6 +3412,9 @@
out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, toolMajor);
out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, toolMinor);
out.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, orientation);
+ if (distance != 0) {
+ out.setAxisValue(AMOTION_EVENT_AXIS_DISTANCE, distance);
+ }
// Write output properties.
PointerProperties& properties = mCurrentTouchProperties[i];
@@ -5020,13 +5146,13 @@
SingleTouchInputMapper::SingleTouchInputMapper(InputDevice* device) :
TouchInputMapper(device) {
- initialize();
+ clearState();
}
SingleTouchInputMapper::~SingleTouchInputMapper() {
}
-void SingleTouchInputMapper::initialize() {
+void SingleTouchInputMapper::clearState() {
mAccumulator.clear();
mDown = false;
@@ -5040,7 +5166,7 @@
void SingleTouchInputMapper::reset() {
TouchInputMapper::reset();
- initialize();
+ clearState();
}
void SingleTouchInputMapper::process(const RawEvent* rawEvent) {
@@ -5144,6 +5270,7 @@
mCurrentTouch.pointers[0].toolMajor = mToolWidth;
mCurrentTouch.pointers[0].toolMinor = mToolWidth;
mCurrentTouch.pointers[0].orientation = 0;
+ mCurrentTouch.pointers[0].distance = 0;
mCurrentTouch.pointers[0].isStylus = false; // TODO: Set stylus
mCurrentTouch.idToIndex[0] = 0;
mCurrentTouch.idBits.markBit(0);
@@ -5168,22 +5295,22 @@
// --- MultiTouchInputMapper ---
MultiTouchInputMapper::MultiTouchInputMapper(InputDevice* device) :
- TouchInputMapper(device) {
- initialize();
+ TouchInputMapper(device), mSlotCount(0), mUsingSlotsProtocol(false) {
+ clearState();
}
MultiTouchInputMapper::~MultiTouchInputMapper() {
}
-void MultiTouchInputMapper::initialize() {
- mAccumulator.clear();
+void MultiTouchInputMapper::clearState() {
+ mAccumulator.clear(mSlotCount);
mButtonState = 0;
}
void MultiTouchInputMapper::reset() {
TouchInputMapper::reset();
- initialize();
+ clearState();
}
void MultiTouchInputMapper::process(const RawEvent* rawEvent) {
@@ -5203,45 +5330,69 @@
}
case EV_ABS: {
- uint32_t pointerIndex = mAccumulator.pointerCount;
- Accumulator::Pointer* pointer = & mAccumulator.pointers[pointerIndex];
+ bool newSlot = false;
+ if (mUsingSlotsProtocol && rawEvent->scanCode == ABS_MT_SLOT) {
+ mAccumulator.currentSlot = rawEvent->value;
+ newSlot = true;
+ }
+
+ if (mAccumulator.currentSlot < 0 || size_t(mAccumulator.currentSlot) >= mSlotCount) {
+ if (newSlot) {
+#if DEBUG_POINTERS
+ LOGW("MultiTouch device %s emitted invalid slot index %d but it "
+ "should be between 0 and %d; ignoring this slot.",
+ getDeviceName().string(), mAccumulator.currentSlot, mSlotCount);
+#endif
+ }
+ break;
+ }
+
+ Accumulator::Slot* slot = &mAccumulator.slots[mAccumulator.currentSlot];
switch (rawEvent->scanCode) {
case ABS_MT_POSITION_X:
- pointer->fields |= Accumulator::FIELD_ABS_MT_POSITION_X;
- pointer->absMTPositionX = rawEvent->value;
+ slot->fields |= Accumulator::FIELD_ABS_MT_POSITION_X;
+ slot->absMTPositionX = rawEvent->value;
break;
case ABS_MT_POSITION_Y:
- pointer->fields |= Accumulator::FIELD_ABS_MT_POSITION_Y;
- pointer->absMTPositionY = rawEvent->value;
+ slot->fields |= Accumulator::FIELD_ABS_MT_POSITION_Y;
+ slot->absMTPositionY = rawEvent->value;
break;
case ABS_MT_TOUCH_MAJOR:
- pointer->fields |= Accumulator::FIELD_ABS_MT_TOUCH_MAJOR;
- pointer->absMTTouchMajor = rawEvent->value;
+ slot->fields |= Accumulator::FIELD_ABS_MT_TOUCH_MAJOR;
+ slot->absMTTouchMajor = rawEvent->value;
break;
case ABS_MT_TOUCH_MINOR:
- pointer->fields |= Accumulator::FIELD_ABS_MT_TOUCH_MINOR;
- pointer->absMTTouchMinor = rawEvent->value;
+ slot->fields |= Accumulator::FIELD_ABS_MT_TOUCH_MINOR;
+ slot->absMTTouchMinor = rawEvent->value;
break;
case ABS_MT_WIDTH_MAJOR:
- pointer->fields |= Accumulator::FIELD_ABS_MT_WIDTH_MAJOR;
- pointer->absMTWidthMajor = rawEvent->value;
+ slot->fields |= Accumulator::FIELD_ABS_MT_WIDTH_MAJOR;
+ slot->absMTWidthMajor = rawEvent->value;
break;
case ABS_MT_WIDTH_MINOR:
- pointer->fields |= Accumulator::FIELD_ABS_MT_WIDTH_MINOR;
- pointer->absMTWidthMinor = rawEvent->value;
+ slot->fields |= Accumulator::FIELD_ABS_MT_WIDTH_MINOR;
+ slot->absMTWidthMinor = rawEvent->value;
break;
case ABS_MT_ORIENTATION:
- pointer->fields |= Accumulator::FIELD_ABS_MT_ORIENTATION;
- pointer->absMTOrientation = rawEvent->value;
+ slot->fields |= Accumulator::FIELD_ABS_MT_ORIENTATION;
+ slot->absMTOrientation = rawEvent->value;
break;
case ABS_MT_TRACKING_ID:
- pointer->fields |= Accumulator::FIELD_ABS_MT_TRACKING_ID;
- pointer->absMTTrackingId = rawEvent->value;
+ if (mUsingSlotsProtocol && rawEvent->value < 0) {
+ slot->clear();
+ } else {
+ slot->fields |= Accumulator::FIELD_ABS_MT_TRACKING_ID;
+ slot->absMTTrackingId = rawEvent->value;
+ }
break;
case ABS_MT_PRESSURE:
- pointer->fields |= Accumulator::FIELD_ABS_MT_PRESSURE;
- pointer->absMTPressure = rawEvent->value;
+ slot->fields |= Accumulator::FIELD_ABS_MT_PRESSURE;
+ slot->absMTPressure = rawEvent->value;
+ break;
+ case ABS_MT_TOOL_TYPE:
+ slot->fields |= Accumulator::FIELD_ABS_MT_TOOL_TYPE;
+ slot->absMTToolType = rawEvent->value;
break;
}
break;
@@ -5251,19 +5402,7 @@
switch (rawEvent->scanCode) {
case SYN_MT_REPORT: {
// MultiTouch Sync: The driver has returned all data for *one* of the pointers.
- uint32_t pointerIndex = mAccumulator.pointerCount;
-
- if (mAccumulator.pointers[pointerIndex].fields) {
- if (pointerIndex == MAX_POINTERS) {
- LOGW("MultiTouch device driver returned more than maximum of %d pointers.",
- MAX_POINTERS);
- } else {
- pointerIndex += 1;
- mAccumulator.pointerCount = pointerIndex;
- }
- }
-
- mAccumulator.pointers[pointerIndex].clear();
+ mAccumulator.currentSlot += 1;
break;
}
@@ -5279,99 +5418,120 @@
static const uint32_t REQUIRED_FIELDS =
Accumulator::FIELD_ABS_MT_POSITION_X | Accumulator::FIELD_ABS_MT_POSITION_Y;
- uint32_t inCount = mAccumulator.pointerCount;
- uint32_t outCount = 0;
+ size_t inCount = mSlotCount;
+ size_t outCount = 0;
bool havePointerIds = true;
mCurrentTouch.clear();
- for (uint32_t inIndex = 0; inIndex < inCount; inIndex++) {
- const Accumulator::Pointer& inPointer = mAccumulator.pointers[inIndex];
- uint32_t fields = inPointer.fields;
+ for (size_t inIndex = 0; inIndex < inCount; inIndex++) {
+ const Accumulator::Slot& inSlot = mAccumulator.slots[inIndex];
+ uint32_t fields = inSlot.fields;
if ((fields & REQUIRED_FIELDS) != REQUIRED_FIELDS) {
// Some drivers send empty MT sync packets without X / Y to indicate a pointer up.
+ // This may also indicate an unused slot.
// Drop this finger.
continue;
}
+ if (outCount >= MAX_POINTERS) {
+#if DEBUG_POINTERS
+ LOGD("MultiTouch device %s emitted more than maximum of %d pointers; "
+ "ignoring the rest.",
+ getDeviceName().string(), MAX_POINTERS);
+#endif
+ break; // too many fingers!
+ }
+
PointerData& outPointer = mCurrentTouch.pointers[outCount];
- outPointer.x = inPointer.absMTPositionX;
- outPointer.y = inPointer.absMTPositionY;
+ outPointer.x = inSlot.absMTPositionX;
+ outPointer.y = inSlot.absMTPositionY;
if (fields & Accumulator::FIELD_ABS_MT_PRESSURE) {
- if (inPointer.absMTPressure <= 0) {
- // Some devices send sync packets with X / Y but with a 0 pressure to indicate
- // a pointer going up. Drop this finger.
- continue;
- }
- outPointer.pressure = inPointer.absMTPressure;
+ outPointer.pressure = inSlot.absMTPressure;
} else {
// Default pressure to 0 if absent.
outPointer.pressure = 0;
}
if (fields & Accumulator::FIELD_ABS_MT_TOUCH_MAJOR) {
- if (inPointer.absMTTouchMajor <= 0) {
+ if (inSlot.absMTTouchMajor <= 0) {
// Some devices send sync packets with X / Y but with a 0 touch major to indicate
// a pointer going up. Drop this finger.
continue;
}
- outPointer.touchMajor = inPointer.absMTTouchMajor;
+ outPointer.touchMajor = inSlot.absMTTouchMajor;
} else {
// Default touch area to 0 if absent.
outPointer.touchMajor = 0;
}
if (fields & Accumulator::FIELD_ABS_MT_TOUCH_MINOR) {
- outPointer.touchMinor = inPointer.absMTTouchMinor;
+ outPointer.touchMinor = inSlot.absMTTouchMinor;
} else {
// Assume touch area is circular.
outPointer.touchMinor = outPointer.touchMajor;
}
if (fields & Accumulator::FIELD_ABS_MT_WIDTH_MAJOR) {
- outPointer.toolMajor = inPointer.absMTWidthMajor;
+ outPointer.toolMajor = inSlot.absMTWidthMajor;
} else {
// Default tool area to 0 if absent.
outPointer.toolMajor = 0;
}
if (fields & Accumulator::FIELD_ABS_MT_WIDTH_MINOR) {
- outPointer.toolMinor = inPointer.absMTWidthMinor;
+ outPointer.toolMinor = inSlot.absMTWidthMinor;
} else {
// Assume tool area is circular.
outPointer.toolMinor = outPointer.toolMajor;
}
if (fields & Accumulator::FIELD_ABS_MT_ORIENTATION) {
- outPointer.orientation = inPointer.absMTOrientation;
+ outPointer.orientation = inSlot.absMTOrientation;
} else {
// Default orientation to vertical if absent.
outPointer.orientation = 0;
}
- outPointer.isStylus = false; // TODO: Handle stylus
+ if (fields & Accumulator::FIELD_ABS_MT_DISTANCE) {
+ outPointer.distance = inSlot.absMTDistance;
+ } else {
+ // Default distance is 0 (direct contact).
+ outPointer.distance = 0;
+ }
+
+ if (fields & Accumulator::FIELD_ABS_MT_TOOL_TYPE) {
+ outPointer.isStylus = (inSlot.absMTToolType == MT_TOOL_PEN);
+ } else {
+ // Assume this is not a stylus.
+ outPointer.isStylus = false;
+ }
// Assign pointer id using tracking id if available.
if (havePointerIds) {
- if (fields & Accumulator::FIELD_ABS_MT_TRACKING_ID) {
- uint32_t id = uint32_t(inPointer.absMTTrackingId);
+ int32_t id;
+ if (mUsingSlotsProtocol) {
+ id = inIndex;
+ } else if (fields & Accumulator::FIELD_ABS_MT_TRACKING_ID) {
+ id = inSlot.absMTTrackingId;
+ } else {
+ id = -1;
+ }
- if (id > MAX_POINTER_ID) {
+ if (id >= 0 && id <= MAX_POINTER_ID) {
+ outPointer.id = id;
+ mCurrentTouch.idToIndex[id] = outCount;
+ mCurrentTouch.idBits.markBit(id);
+ } else {
+ if (id >= 0) {
#if DEBUG_POINTERS
- LOGD("Pointers: Ignoring driver provided pointer id %d because "
- "it is larger than max supported id %d",
+ LOGD("Pointers: Ignoring driver provided slot index or tracking id %d because "
+ "it is larger than the maximum supported pointer id %d",
id, MAX_POINTER_ID);
#endif
- havePointerIds = false;
}
- else {
- outPointer.id = id;
- mCurrentTouch.idToIndex[id] = outCount;
- mCurrentTouch.idBits.markBit(id);
- }
- } else {
havePointerIds = false;
}
}
@@ -5386,20 +5546,40 @@
syncTouch(when, havePointerIds);
- mAccumulator.clear();
+ mAccumulator.clear(mUsingSlotsProtocol ? 0 : mSlotCount);
}
void MultiTouchInputMapper::configureRawAxes() {
TouchInputMapper::configureRawAxes();
- getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_POSITION_X, & mRawAxes.x);
- getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_POSITION_Y, & mRawAxes.y);
- getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_TOUCH_MAJOR, & mRawAxes.touchMajor);
- getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_TOUCH_MINOR, & mRawAxes.touchMinor);
- getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_WIDTH_MAJOR, & mRawAxes.toolMajor);
- getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_WIDTH_MINOR, & mRawAxes.toolMinor);
- getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_ORIENTATION, & mRawAxes.orientation);
- getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_PRESSURE, & mRawAxes.pressure);
+ getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_POSITION_X, &mRawAxes.x);
+ getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_POSITION_Y, &mRawAxes.y);
+ getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_TOUCH_MAJOR, &mRawAxes.touchMajor);
+ getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_TOUCH_MINOR, &mRawAxes.touchMinor);
+ getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_WIDTH_MAJOR, &mRawAxes.toolMajor);
+ getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_WIDTH_MINOR, &mRawAxes.toolMinor);
+ getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_ORIENTATION, &mRawAxes.orientation);
+ getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_PRESSURE, &mRawAxes.pressure);
+ getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_DISTANCE, &mRawAxes.distance);
+ getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_TRACKING_ID, &mRawAxes.trackingId);
+ getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_SLOT, &mRawAxes.slot);
+
+ if (mRawAxes.trackingId.valid
+ && mRawAxes.slot.valid && mRawAxes.slot.minValue == 0 && mRawAxes.slot.maxValue > 0) {
+ mSlotCount = mRawAxes.slot.maxValue + 1;
+ if (mSlotCount > MAX_SLOTS) {
+ LOGW("MultiTouch Device %s reported %d slots but the framework "
+ "only supports a maximum of %d slots at this time.",
+ getDeviceName().string(), mSlotCount, MAX_SLOTS);
+ mSlotCount = MAX_SLOTS;
+ }
+ mUsingSlotsProtocol = true;
+ } else {
+ mSlotCount = MAX_POINTERS;
+ mUsingSlotsProtocol = false;
+ }
+
+ mAccumulator.allocateSlots(mSlotCount);
}
diff --git a/services/input/InputReader.h b/services/input/InputReader.h
index 62ac4b2..85338b6 100644
--- a/services/input/InputReader.h
+++ b/services/input/InputReader.h
@@ -331,6 +331,7 @@
String8 mName;
uint32_t mSources;
bool mIsExternal;
+ bool mDropUntilNextSync;
typedef int32_t (InputMapper::*GetStateFunc)(uint32_t sourceMask, int32_t code);
int32_t getState(uint32_t sourceMask, int32_t code, GetStateFunc getStateFunc);
@@ -602,6 +603,7 @@
int32_t toolMajor;
int32_t toolMinor;
int32_t orientation;
+ int32_t distance;
bool isStylus;
inline bool operator== (const PointerData& other) const {
@@ -613,7 +615,8 @@
&& touchMinor == other.touchMinor
&& toolMajor == other.toolMajor
&& toolMinor == other.toolMinor
- && orientation == other.orientation;
+ && orientation == other.orientation
+ && distance == other.distance;
}
inline bool operator!= (const PointerData& other) const {
return !(*this == other);
@@ -759,6 +762,17 @@
};
OrientationCalibration orientationCalibration;
+
+ // Distance
+ enum DistanceCalibration {
+ DISTANCE_CALIBRATION_DEFAULT,
+ DISTANCE_CALIBRATION_NONE,
+ DISTANCE_CALIBRATION_SCALED,
+ };
+
+ DistanceCalibration distanceCalibration;
+ bool haveDistanceScale;
+ float distanceScale;
} mCalibration;
// Raw axis information from the driver.
@@ -771,6 +785,9 @@
RawAbsoluteAxisInfo toolMajor;
RawAbsoluteAxisInfo toolMinor;
RawAbsoluteAxisInfo orientation;
+ RawAbsoluteAxisInfo distance;
+ RawAbsoluteAxisInfo trackingId;
+ RawAbsoluteAxisInfo slot;
} mRawAxes;
// Current and previous touch sample data.
@@ -819,6 +836,8 @@
float orientationScale;
+ float distanceScale;
+
// Oriented motion ranges for input device info.
struct OrientedRanges {
InputDeviceInfo::MotionRange x;
@@ -840,6 +859,9 @@
bool haveOrientation;
InputDeviceInfo::MotionRange orientation;
+
+ bool haveDistance;
+ InputDeviceInfo::MotionRange distance;
} orientedRanges;
// Oriented dimensions and precision.
@@ -1146,7 +1168,7 @@
int32_t mToolWidth;
int32_t mButtonState;
- void initialize();
+ void clearState();
void sync(nsecs_t when);
};
@@ -1166,20 +1188,21 @@
private:
struct Accumulator {
enum {
- FIELD_ABS_MT_POSITION_X = 1,
- FIELD_ABS_MT_POSITION_Y = 2,
- FIELD_ABS_MT_TOUCH_MAJOR = 4,
- FIELD_ABS_MT_TOUCH_MINOR = 8,
- FIELD_ABS_MT_WIDTH_MAJOR = 16,
- FIELD_ABS_MT_WIDTH_MINOR = 32,
- FIELD_ABS_MT_ORIENTATION = 64,
- FIELD_ABS_MT_TRACKING_ID = 128,
- FIELD_ABS_MT_PRESSURE = 256,
+ FIELD_ABS_MT_POSITION_X = 1 << 0,
+ FIELD_ABS_MT_POSITION_Y = 1 << 1,
+ FIELD_ABS_MT_TOUCH_MAJOR = 1 << 2,
+ FIELD_ABS_MT_TOUCH_MINOR = 1 << 3,
+ FIELD_ABS_MT_WIDTH_MAJOR = 1 << 4,
+ FIELD_ABS_MT_WIDTH_MINOR = 1 << 5,
+ FIELD_ABS_MT_ORIENTATION = 1 << 6,
+ FIELD_ABS_MT_TRACKING_ID = 1 << 7,
+ FIELD_ABS_MT_PRESSURE = 1 << 8,
+ FIELD_ABS_MT_TOOL_TYPE = 1 << 9,
+ FIELD_ABS_MT_DISTANCE = 1 << 10,
};
- uint32_t pointerCount;
- struct Pointer {
- uint32_t fields;
+ struct Slot {
+ uint32_t fields; // 0 if slot is unused
int32_t absMTPositionX;
int32_t absMTPositionY;
@@ -1190,27 +1213,56 @@
int32_t absMTOrientation;
int32_t absMTTrackingId;
int32_t absMTPressure;
+ int32_t absMTToolType;
+ int32_t absMTDistance;
+
+ inline Slot() {
+ clear();
+ }
inline void clear() {
fields = 0;
}
- } pointers[MAX_POINTERS + 1]; // + 1 to remove the need for extra range checks
+ };
+
+ // Current slot index.
+ int32_t currentSlot;
+
+ // Array of slots.
+ Slot* slots;
// Bitfield of buttons that went down or up.
uint32_t buttonDown;
uint32_t buttonUp;
- inline void clear() {
- pointerCount = 0;
- pointers[0].clear();
+ Accumulator() : slots(NULL) {
+ clear(false);
+ }
+
+ ~Accumulator() {
+ delete[] slots;
+ }
+
+ void allocateSlots(size_t slotCount) {
+ slots = new Slot[slotCount];
+ }
+
+ void clear(size_t slotCount) {
+ for (size_t i = 0; i < slotCount; i++) {
+ slots[i].clear();
+ }
+ currentSlot = 0;
buttonDown = 0;
buttonUp = 0;
}
} mAccumulator;
+ size_t mSlotCount;
+ bool mUsingSlotsProtocol;
+
int32_t mButtonState;
- void initialize();
+ void clearState();
void sync(nsecs_t when);
};
diff --git a/services/input/tests/InputReader_test.cpp b/services/input/tests/InputReader_test.cpp
index f5d7ae8..1ab2a3e 100644
--- a/services/input/tests/InputReader_test.cpp
+++ b/services/input/tests/InputReader_test.cpp
@@ -613,6 +613,10 @@
return false;
}
+ virtual bool hasInputProperty(int32_t deviceId, int property) const {
+ return false;
+ }
+
virtual status_t mapKey(int32_t deviceId, int scancode,
int32_t* outKeycode, uint32_t* outFlags) const {
Device* device = getDevice(deviceId);
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
index ae04b7f..f78dca9 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -163,6 +163,7 @@
private final ArrayList<StorageVolume> mVolumes = new ArrayList<StorageVolume>();
private StorageVolume mPrimaryVolume;
private final HashMap<String, String> mVolumeStates = new HashMap<String, String>();
+ private final HashMap<String, StorageVolume> mVolumeMap = new HashMap<String, StorageVolume>();
private String mExternalStoragePath;
private PackageManagerService mPms;
private boolean mUmsEnabling;
@@ -672,8 +673,6 @@
* Callback from NativeDaemonConnector
*/
public boolean onEvent(int code, String raw, String[] cooked) {
- Intent in = null;
-
if (DEBUG_EVENTS) {
StringBuilder builder = new StringBuilder();
builder.append("onEvent::");
@@ -708,6 +707,7 @@
// FMT: NNN Volume <label> <mountpoint> disk inserted (<major>:<minor>)
// FMT: NNN Volume <label> <mountpoint> disk removed (<major>:<minor>)
// FMT: NNN Volume <label> <mountpoint> bad removal (<major>:<minor>)
+ String action = null;
final String label = cooked[2];
final String path = cooked[3];
int major = -1;
@@ -746,32 +746,31 @@
/* Send the media unmounted event first */
if (DEBUG_EVENTS) Slog.i(TAG, "Sending unmounted event first");
updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
- in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
- mContext.sendBroadcast(in);
+ sendStorageIntent(Environment.MEDIA_UNMOUNTED, path);
if (DEBUG_EVENTS) Slog.i(TAG, "Sending media removed");
updatePublicVolumeState(path, Environment.MEDIA_REMOVED);
- in = new Intent(Intent.ACTION_MEDIA_REMOVED, Uri.parse("file://" + path));
+ action = Intent.ACTION_MEDIA_REMOVED;
} else if (code == VoldResponseCode.VolumeBadRemoval) {
if (DEBUG_EVENTS) Slog.i(TAG, "Sending unmounted event first");
/* Send the media unmounted event first */
updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
- in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
- mContext.sendBroadcast(in);
+ action = Intent.ACTION_MEDIA_UNMOUNTED;
if (DEBUG_EVENTS) Slog.i(TAG, "Sending media bad removal");
updatePublicVolumeState(path, Environment.MEDIA_BAD_REMOVAL);
- in = new Intent(Intent.ACTION_MEDIA_BAD_REMOVAL, Uri.parse("file://" + path));
+ action = Intent.ACTION_MEDIA_BAD_REMOVAL;
} else {
Slog.e(TAG, String.format("Unknown code {%d}", code));
}
+
+ if (action != null) {
+ sendStorageIntent(action, path);
+ }
} else {
return false;
}
- if (in != null) {
- mContext.sendBroadcast(in);
- }
return true;
}
@@ -779,12 +778,11 @@
String vs = getVolumeState(path);
if (DEBUG_EVENTS) Slog.i(TAG, "notifyVolumeStateChanged::" + vs);
- Intent in = null;
+ String action = null;
if (oldState == VolumeState.Shared && newState != oldState) {
if (LOCAL_LOGD) Slog.d(TAG, "Sending ACTION_MEDIA_UNSHARED intent");
- mContext.sendBroadcast(new Intent(Intent.ACTION_MEDIA_UNSHARED,
- Uri.parse("file://" + path)));
+ sendStorageIntent(Intent.ACTION_MEDIA_UNSHARED, path);
}
if (newState == VolumeState.Init) {
@@ -801,31 +799,29 @@
Environment.MEDIA_UNMOUNTABLE) && !getUmsEnabling()) {
if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state for media bad removal nofs and unmountable");
updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
- in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
+ action = Intent.ACTION_MEDIA_UNMOUNTED;
}
} else if (newState == VolumeState.Pending) {
} else if (newState == VolumeState.Checking) {
if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state checking");
updatePublicVolumeState(path, Environment.MEDIA_CHECKING);
- in = new Intent(Intent.ACTION_MEDIA_CHECKING, Uri.parse("file://" + path));
+ action = Intent.ACTION_MEDIA_CHECKING;
} else if (newState == VolumeState.Mounted) {
if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state mounted");
updatePublicVolumeState(path, Environment.MEDIA_MOUNTED);
- in = new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://" + path));
- in.putExtra("read-only", false);
+ action = Intent.ACTION_MEDIA_MOUNTED;
} else if (newState == VolumeState.Unmounting) {
- in = new Intent(Intent.ACTION_MEDIA_EJECT, Uri.parse("file://" + path));
+ action = Intent.ACTION_MEDIA_EJECT;
} else if (newState == VolumeState.Formatting) {
} else if (newState == VolumeState.Shared) {
if (DEBUG_EVENTS) Slog.i(TAG, "Updating volume state media mounted");
/* Send the media unmounted event first */
updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
- in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
- mContext.sendBroadcast(in);
+ sendStorageIntent(Intent.ACTION_MEDIA_UNMOUNTED, path);
if (DEBUG_EVENTS) Slog.i(TAG, "Updating media shared");
updatePublicVolumeState(path, Environment.MEDIA_SHARED);
- in = new Intent(Intent.ACTION_MEDIA_SHARED, Uri.parse("file://" + path));
+ action = Intent.ACTION_MEDIA_SHARED;
if (LOCAL_LOGD) Slog.d(TAG, "Sending ACTION_MEDIA_SHARED intent");
} else if (newState == VolumeState.SharedMnt) {
Slog.e(TAG, "Live shared mounts not supported yet!");
@@ -834,8 +830,8 @@
Slog.e(TAG, "Unhandled VolumeState {" + newState + "}");
}
- if (in != null) {
- mContext.sendBroadcast(in);
+ if (action != null) {
+ sendStorageIntent(action, path);
}
}
@@ -885,7 +881,7 @@
/*
* Mount failed for some reason
*/
- Intent in = null;
+ String action = null;
int code = e.getCode();
if (code == VoldResponseCode.OpFailedNoMedia) {
/*
@@ -898,7 +894,7 @@
* Media is blank or does not contain a supported filesystem
*/
updatePublicVolumeState(path, Environment.MEDIA_NOFS);
- in = new Intent(Intent.ACTION_MEDIA_NOFS, Uri.parse("file://" + path));
+ action = Intent.ACTION_MEDIA_NOFS;
rc = StorageResultCode.OperationFailedMediaBlank;
} else if (code == VoldResponseCode.OpFailedMediaCorrupt) {
if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state media corrupt");
@@ -906,7 +902,7 @@
* Volume consistency check failed
*/
updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTABLE);
- in = new Intent(Intent.ACTION_MEDIA_UNMOUNTABLE, Uri.parse("file://" + path));
+ action = Intent.ACTION_MEDIA_UNMOUNTABLE;
rc = StorageResultCode.OperationFailedMediaCorrupt;
} else {
rc = StorageResultCode.OperationFailedInternalError;
@@ -915,8 +911,8 @@
/*
* Send broadcast intent (if required for the failure)
*/
- if (in != null) {
- mContext.sendBroadcast(in);
+ if (action != null) {
+ sendStorageIntent(action, path);
}
}
@@ -1073,6 +1069,14 @@
}
}
+ private void sendStorageIntent(String action, String path) {
+ Intent intent = new Intent(action, Uri.parse("file://" + path));
+ // add StorageVolume extra
+ intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, mVolumeMap.get(path));
+ Slog.d(TAG, "sendStorageIntent " + intent);
+ mContext.sendBroadcast(intent);
+ }
+
private void sendUmsIntent(boolean c) {
mContext.sendBroadcast(
new Intent((c ? Intent.ACTION_UMS_CONNECTED : Intent.ACTION_UMS_DISCONNECTED)));
@@ -1124,7 +1128,8 @@
if (path == null || description == null) {
Slog.e(TAG, "path or description is null in readStorageList");
} else {
- StorageVolume volume = new StorageVolume(path.toString(),
+ String pathString = path.toString();
+ StorageVolume volume = new StorageVolume(pathString,
description.toString(), removable, emulated, mtpReserve);
if (primary) {
if (mPrimaryVolume == null) {
@@ -1139,6 +1144,7 @@
} else {
mVolumes.add(volume);
}
+ mVolumeMap.put(pathString, volume);
}
a.recycle();
}
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 568183b..cf5592c 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -853,14 +853,6 @@
* Current sequence id for process LRU updating.
*/
int mLruSeq = 0;
-
- /**
- * Set to true if the ANDROID_SIMPLE_PROCESS_MANAGEMENT envvar
- * is set, indicating the user wants processes started in such a way
- * that they can use ANDROID_PROCESS_WRAPPER and know what will be
- * running in each process (thus no pre-initialized process, etc).
- */
- boolean mSimpleProcessManagement = false;
/**
* System monitoring: number of processes that died since the last
@@ -1455,15 +1447,6 @@
}
private ActivityManagerService() {
- String v = System.getenv("ANDROID_SIMPLE_PROCESS_MANAGEMENT");
- if (v != null && Integer.getInteger(v) != 0) {
- mSimpleProcessManagement = true;
- }
- v = System.getenv("ANDROID_DEBUG_APP");
- if (v != null) {
- mSimpleProcessManagement = true;
- }
-
Slog.i(TAG, "Memory class: " + ActivityManager.staticGetMemoryClass());
File dataDir = Environment.getDataDirectory();
@@ -1935,8 +1918,7 @@
debugFlags |= Zygote.DEBUG_ENABLE_ASSERT;
}
int pid = Process.start("android.app.ActivityThread",
- mSimpleProcessManagement ? app.processName : null, uid, uid,
- gids, debugFlags, null);
+ app.processName, uid, uid, gids, debugFlags, null);
BatteryStatsImpl bs = app.batteryStats.getBatteryStats();
synchronized (bs) {
if (bs.isOnBattery()) {
diff --git a/services/java/com/android/server/location/GpsLocationProvider.java b/services/java/com/android/server/location/GpsLocationProvider.java
index b1ab05b..67e73f5f 100755
--- a/services/java/com/android/server/location/GpsLocationProvider.java
+++ b/services/java/com/android/server/location/GpsLocationProvider.java
@@ -277,8 +277,8 @@
private final SparseIntArray mClientUids = new SparseIntArray();
// how often to request NTP time, in milliseconds
- // current setting 4 hours
- private static final long NTP_INTERVAL = 4*60*60*1000;
+ // current setting 24 hours
+ private static final long NTP_INTERVAL = 24*60*60*1000;
// how long to wait if we have a network error in NTP or XTRA downloading
// current setting - 5 minutes
private static final long RETRY_INTERVAL = 5*60*1000;
diff --git a/telephony/java/com/android/internal/telephony/DataConnection.java b/telephony/java/com/android/internal/telephony/DataConnection.java
index dc07ef2..3636baa 100644
--- a/telephony/java/com/android/internal/telephony/DataConnection.java
+++ b/telephony/java/com/android/internal/telephony/DataConnection.java
@@ -205,6 +205,7 @@
protected long lastFailTime;
protected FailCause lastFailCause;
protected static final String NULL_IP = "0.0.0.0";
+ private int mRefCount;
Object userData;
//***** Abstract methods
@@ -413,49 +414,6 @@
return mRetryMgr.configure(configStr);
}
- private AtomicInteger mRefCount = new AtomicInteger(0);
-
- /**
- * Set refCount.
- *
- * @param val is new refCount
- */
- public void setRefCount(int val) {
- mRefCount.set(val);
- }
-
- /**
- * Get refCount
- *
- * @return refCount
- */
- public int getRefCount() {
- return mRefCount.get();
- }
-
- /**
- * @return decrement and return refCount
- *
- * TODO: Consider using the refCount for defining the
- * life time of a connection. When this goes zero the
- * DataConnection could tear itself down.
- */
- public int decAndGetRefCount() {
- int v = mRefCount.decrementAndGet();
- if (v < 0) {
- log("BUG: decAndGetRefCount caused refCount to be < 0");
- mRefCount.set(0);
- }
- return v;
- }
-
- /**
- * @return increment and return refCount
- */
- public int incAndGetRefCount() {
- return mRefCount.incrementAndGet();
- }
-
/*
* **************************************************************************
* End members owned by DataConnectionTracker
@@ -471,6 +429,7 @@
createTime = -1;
lastFailTime = -1;
lastFailCause = FailCause.NONE;
+ mRefCount = 0;
mLinkProperties = new LinkProperties();
mApn = null;
@@ -674,6 +633,11 @@
mAc.replyToMessage(msg, DataConnectionAc.RSP_RESET);
transitionTo(mInactiveState);
break;
+ case DataConnectionAc.REQ_GET_REFCOUNT: {
+ log("REQ_GET_REFCOUNT refCount=" + mRefCount);
+ mAc.replyToMessage(msg, DataConnectionAc.RSP_GET_REFCOUNT, mRefCount);
+ break;
+ }
case EVENT_CONNECT:
if (DBG) log("DcDefaultState: msg.what=EVENT_CONNECT, fail not expected");
@@ -774,9 +738,13 @@
break;
case EVENT_CONNECT:
- if (DBG) log("DcInactiveState msg.what=EVENT_CONNECT");
ConnectionParams cp = (ConnectionParams) msg.obj;
cp.tag = mTag;
+ if (DBG) {
+ log("DcInactiveState msg.what=EVENT_CONNECT." + "RefCount = "
+ + mRefCount);
+ }
+ mRefCount = 1;
onConnect(cp);
transitionTo(mActivatingState);
retVal = HANDLED;
@@ -804,7 +772,15 @@
switch (msg.what) {
case EVENT_DISCONNECT:
- if (DBG) log("DcActivatingState deferring msg.what=EVENT_DISCONNECT");
+ if (DBG) log("DcActivatingState deferring msg.what=EVENT_DISCONNECT"
+ + mRefCount);
+ deferMessage(msg);
+ retVal = HANDLED;
+ break;
+
+ case EVENT_CONNECT:
+ if (DBG) log("DcActivatingState deferring msg.what=EVENT_CONNECT refCount = "
+ + mRefCount);
deferMessage(msg);
retVal = HANDLED;
break;
@@ -928,12 +904,28 @@
boolean retVal;
switch (msg.what) {
+ case EVENT_CONNECT:
+ mRefCount++;
+ if (DBG) log("DcActiveState msg.what=EVENT_CONNECT RefCount=" + mRefCount);
+ if (msg.obj != null) {
+ notifyConnectCompleted((ConnectionParams) msg.obj, FailCause.NONE);
+ }
+ retVal = HANDLED;
+ break;
case EVENT_DISCONNECT:
- if (DBG) log("DcActiveState msg.what=EVENT_DISCONNECT");
- DisconnectParams dp = (DisconnectParams) msg.obj;
- dp.tag = mTag;
- tearDownData(dp);
- transitionTo(mDisconnectingState);
+ mRefCount--;
+ if (DBG) log("DcActiveState msg.what=EVENT_DISCONNECT RefCount=" + mRefCount);
+ if (mRefCount == 0)
+ {
+ DisconnectParams dp = (DisconnectParams) msg.obj;
+ dp.tag = mTag;
+ tearDownData(dp);
+ transitionTo(mDisconnectingState);
+ } else {
+ if (msg.obj != null) {
+ notifyDisconnectCompleted((DisconnectParams) msg.obj);
+ }
+ }
retVal = HANDLED;
break;
@@ -956,6 +948,13 @@
boolean retVal;
switch (msg.what) {
+ case EVENT_CONNECT:
+ if (DBG) log("DcDisconnectingState msg.what=EVENT_CONNECT. Defer. RefCount = "
+ + mRefCount);
+ deferMessage(msg);
+ retVal = HANDLED;
+ break;
+
case EVENT_DEACTIVATE_DONE:
if (DBG) log("DcDisconnectingState msg.what=EVENT_DEACTIVATE_DONE");
AsyncResult ar = (AsyncResult) msg.obj;
diff --git a/telephony/java/com/android/internal/telephony/DataConnectionAc.java b/telephony/java/com/android/internal/telephony/DataConnectionAc.java
index 939bab2..a0d9b0f 100644
--- a/telephony/java/com/android/internal/telephony/DataConnectionAc.java
+++ b/telephony/java/com/android/internal/telephony/DataConnectionAc.java
@@ -59,6 +59,9 @@
public static final int REQ_RESET = BASE + 14;
public static final int RSP_RESET = BASE + 15;
+ public static final int REQ_GET_REFCOUNT = BASE + 16;
+ public static final int RSP_GET_REFCOUNT = BASE + 17;
+
/**
* enum used to notify action taken or necessary to be
* taken after the link property is changed.
@@ -152,6 +155,40 @@
}
/**
+ * Request the Reference Count.
+ * Response {@link #rspRefCount}
+ */
+ public void reqRefCount() {
+ sendMessage(REQ_GET_REFCOUNT);
+ if (DBG) log("reqRefCount");
+ }
+
+ /**
+ * Evaluate a RSP_GET_REFCOUNT message and return the refCount.
+ *
+ * @param response Message
+ * @return ref count or -1 if an error
+ */
+ public int rspRefCount(Message response) {
+ int retVal = response.arg1;
+ if (DBG) log("rspRefCount=" + retVal);
+ return retVal;
+ }
+
+ /**
+ * @return connection id or -1 if an error
+ */
+ public int getRefCountSync() {
+ Message response = sendMessageSynchronously(REQ_GET_REFCOUNT);
+ if ((response != null) && (response.what == RSP_GET_REFCOUNT)) {
+ return rspRefCount(response);
+ } else {
+ log("rspRefCount error response=" + response);
+ return -1;
+ }
+ }
+
+ /**
* Request the connections ApnSetting.
* Response {@link #rspApnSetting}
*/
diff --git a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
index b7ac879..5ddfcd1 100644
--- a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
@@ -987,7 +987,7 @@
resetAllRetryCounts();
onTrySetupData(Phone.REASON_DATA_ENABLED);
} else {
- onCleanUpConnection(true, APN_DEFAULT_ID, Phone.REASON_DATA_DISABLED);
+ onCleanUpAllConnections(Phone.REASON_DATA_DISABLED);
}
}
}
diff --git a/telephony/java/com/android/internal/telephony/cat/CatService.java b/telephony/java/com/android/internal/telephony/cat/CatService.java
index 33cc97e..fb53686 100644
--- a/telephony/java/com/android/internal/telephony/cat/CatService.java
+++ b/telephony/java/com/android/internal/telephony/cat/CatService.java
@@ -32,6 +32,7 @@
import java.io.ByteArrayOutputStream;
+import java.util.Locale;
/**
* Enumeration for representing the tag value of COMPREHENSION-TLV objects. If
@@ -273,8 +274,20 @@
sendTerminalResponse(cmdParams.cmdDet, ResultCode.OK, false, 0, null);
break;
case PROVIDE_LOCAL_INFORMATION:
- sendTerminalResponse(cmdParams.cmdDet, ResultCode.OK, false, 0, null);
- return;
+ ResponseData resp;
+ switch (cmdParams.cmdDet.commandQualifier) {
+ case CommandParamsFactory.DTTZ_SETTING:
+ resp = new DTTZResponseData(null);
+ sendTerminalResponse(cmdParams.cmdDet, ResultCode.OK, false, 0, resp);
+ break;
+ case CommandParamsFactory.LANGUAGE_SETTING:
+ resp = new LanguageResponseData(Locale.getDefault().getLanguage());
+ sendTerminalResponse(cmdParams.cmdDet, ResultCode.OK, false, 0, resp);
+ break;
+ default:
+ sendTerminalResponse(cmdParams.cmdDet, ResultCode.OK, false, 0, null);
+ return;
+ }
case LAUNCH_BROWSER:
case SELECT_ITEM:
case GET_INPUT:
diff --git a/telephony/java/com/android/internal/telephony/cat/CommandParamsFactory.java b/telephony/java/com/android/internal/telephony/cat/CommandParamsFactory.java
index 12204a0..686fe46 100644
--- a/telephony/java/com/android/internal/telephony/cat/CommandParamsFactory.java
+++ b/telephony/java/com/android/internal/telephony/cat/CommandParamsFactory.java
@@ -53,6 +53,7 @@
static final int REFRESH_UICC_RESET = 0x04;
// Command Qualifier values for PLI command
+ static final int DTTZ_SETTING = 0x03;
static final int LANGUAGE_SETTING = 0x04;
static synchronized CommandParamsFactory getInstance(RilMessageDecoder caller,
@@ -883,6 +884,10 @@
throws ResultException {
CatLog.d(this, "process ProvideLocalInfo");
switch (cmdDet.commandQualifier) {
+ case DTTZ_SETTING:
+ CatLog.d(this, "PLI [DTTZ_SETTING]");
+ mCmdParams = new CommandParams(cmdDet);
+ break;
case LANGUAGE_SETTING:
CatLog.d(this, "PLI [LANGUAGE_SETTING]");
mCmdParams = new CommandParams(cmdDet);
diff --git a/telephony/java/com/android/internal/telephony/cat/ResponseData.java b/telephony/java/com/android/internal/telephony/cat/ResponseData.java
index 95f0399..55a2b63 100644
--- a/telephony/java/com/android/internal/telephony/cat/ResponseData.java
+++ b/telephony/java/com/android/internal/telephony/cat/ResponseData.java
@@ -18,6 +18,8 @@
import com.android.internal.telephony.EncodeException;
import com.android.internal.telephony.GsmAlphabet;
+import java.util.Calendar;
+import com.android.internal.telephony.cat.AppInterface.CommandType;
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
@@ -147,4 +149,109 @@
}
}
+// For "PROVIDE LOCAL INFORMATION" command.
+// See TS 31.111 section 6.4.15/ETSI TS 102 223
+// TS 31.124 section 27.22.4.15 for test spec
+class LanguageResponseData extends ResponseData {
+ private String lang;
+
+ public LanguageResponseData(String lang) {
+ super();
+ this.lang = lang;
+ }
+
+ @Override
+ public void format(ByteArrayOutputStream buf) {
+ if (buf == null) {
+ return;
+ }
+
+ // Text string object
+ int tag = 0x80 | ComprehensionTlvTag.LANGUAGE.value();
+ buf.write(tag); // tag
+
+ byte[] data;
+
+ if (lang != null && lang.length() > 0) {
+ data = GsmAlphabet.stringToGsm8BitPacked(lang);
+ }
+ else {
+ data = new byte[0];
+ }
+
+ buf.write(data.length);
+
+ for (byte b : data) {
+ buf.write(b);
+ }
+ }
+}
+
+// For "PROVIDE LOCAL INFORMATION" command.
+// See TS 31.111 section 6.4.15/ETSI TS 102 223
+// TS 31.124 section 27.22.4.15 for test spec
+class DTTZResponseData extends ResponseData {
+ private Calendar calendar;
+
+ public DTTZResponseData(Calendar cal) {
+ super();
+ calendar = cal;
+ }
+
+ @Override
+ public void format(ByteArrayOutputStream buf) {
+ if (buf == null) {
+ return;
+ }
+
+ // DTTZ object
+ int tag = 0x80 | CommandType.PROVIDE_LOCAL_INFORMATION.value();
+ buf.write(tag); // tag
+
+ byte[] data = new byte[8];
+ byte btmp; // temp variable
+
+ data[0] = 0x07; // Write length of DTTZ data
+
+ if (calendar == null) {
+ calendar = Calendar.getInstance();
+ }
+ // Fill year byte
+ btmp = (byte) (calendar.get(java.util.Calendar.YEAR) % 100);
+ data[1] = (byte) (btmp / 10);
+ data[1] += (byte) ((btmp % 10) << 4);
+
+ // Fill month byte
+ btmp = (byte) (calendar.get(java.util.Calendar.MONTH) + 1);
+ data[2] = (byte) (btmp / 10);
+ data[2] += (byte) ((btmp % 10) << 4);
+
+ // Fill day byte
+ btmp = (byte) (calendar.get(java.util.Calendar.DATE));
+ data[3] = (byte) (btmp / 10);
+ data[3] += (byte) ((btmp % 10) << 4);
+
+ // Fill hour byte
+ btmp = (byte) (calendar.get(java.util.Calendar.HOUR_OF_DAY));
+ data[4] = (byte) (btmp / 10);
+ data[4] += (byte) ((btmp % 10) << 4);
+
+ // Fill minute byte
+ btmp = (byte) (calendar.get(java.util.Calendar.MINUTE));
+ data[5] = (byte) (btmp / 10);
+ data[5] += (byte) ((btmp % 10) << 4);
+
+ // Fill second byte
+ btmp = (byte) (calendar.get(java.util.Calendar.SECOND));
+ data[6] = (byte) (btmp / 10);
+ data[6] += (byte) ((btmp % 10) << 4);
+
+ // No time zone info
+ data[7] = (byte) 0xFF;
+
+ for (byte b : data) {
+ buf.write(b);
+ }
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java
index 375d0d1..e3e3d78 100644
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java
@@ -521,19 +521,7 @@
* TODO: Make this configurable?
*/
int nextReconnectDelay = mDataConnections.get(0).getRetryTimer();
- log("Data Connection activate failed. Scheduling next attempt for "
- + (nextReconnectDelay / 1000) + "s");
-
- AlarmManager am =
- (AlarmManager) mPhone.getContext().getSystemService(Context.ALARM_SERVICE);
- Intent intent = new Intent(INTENT_RECONNECT_ALARM);
- intent.putExtra(INTENT_RECONNECT_ALARM_EXTRA_REASON, reason);
- mReconnectIntent = PendingIntent.getBroadcast(
- mPhone.getContext(), 0, intent, 0);
- am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
- SystemClock.elapsedRealtime() + nextReconnectDelay,
- mReconnectIntent);
-
+ startAlarmForReconnect(nextReconnectDelay, reason);
mDataConnections.get(0).increaseRetryCount();
if (!shouldPostNotification(lastFailCauseCode)) {
@@ -545,6 +533,22 @@
}
}
+ private void startAlarmForReconnect(int delay, String reason) {
+
+ log("Data Connection activate failed. Scheduling next attempt for "
+ + (delay / 1000) + "s");
+
+ AlarmManager am =
+ (AlarmManager) mPhone.getContext().getSystemService(Context.ALARM_SERVICE);
+ Intent intent = new Intent(INTENT_RECONNECT_ALARM);
+ intent.putExtra(INTENT_RECONNECT_ALARM_EXTRA_REASON, reason);
+ mReconnectIntent = PendingIntent.getBroadcast(
+ mPhone.getContext(), 0, intent, 0);
+ am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ SystemClock.elapsedRealtime() + delay, mReconnectIntent);
+
+ }
+
private void notifyNoData(FailCause lastFailCauseCode) {
setState(State.FAILED);
notifyDataAvailability(null);
@@ -702,7 +706,7 @@
mActiveApn = null;
if (retryAfterDisconnected(reason)) {
// Wait a bit before trying, so we're not tying up RIL command channel.
- sendMessageDelayed(obtainMessage(EVENT_TRY_SETUP_DATA, reason), APN_DELAY_MILLIS);
+ startAlarmForReconnect(APN_DELAY_MILLIS, reason);
}
}
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
index a48202f..e1a6fef 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
@@ -612,28 +612,26 @@
*
* @param apnContext
* @param tearDown
- * @return refCount
+ * @return none
*/
- private int releaseApnContext(ApnContext apnContext, boolean tearDown) {
+ private void releaseApnContext(ApnContext apnContext, boolean tearDown) {
if (apnContext == null) {
if (DBG) loge("releaseApnContext: apnContext null should not happen, ignore");
- return -1;
+ return;
}
DataConnection dc = apnContext.getDataConnection();
if (dc == null) {
if (DBG) loge("releaseApnContext: apnContext dc == null should not happen, ignore");
- return -1;
+ return;
}
- int refCount = dc.decAndGetRefCount();
- if (DBG) log("releaseApnContext: dec refCount=" + refCount + " tearDown=" + tearDown);
- if (tearDown && (refCount == 0)) {
+ if (tearDown) {
if (DBG) log("releaseApnContext: tearing down");
Message msg = obtainMessage(EVENT_DISCONNECT_DONE, apnContext);
apnContext.getDataConnection().tearDown(apnContext.getReason(), msg);
}
apnContext.setDataConnection(null);
apnContext.setDataConnectionAc(null);
- return refCount;
+ return;
}
private void setupDataOnReadyApns(String reason) {
@@ -809,12 +807,7 @@
apnContext.setState(State.DISCONNECTING);
releaseApnContext(apnContext, tearDown);
} else {
- // STOPSHIP: Reference counting logic in GDCT still have issue.
- // Need to be cleaned up in later patch
dcac.resetSync();
- if (apnContext.getDataConnection() != null) {
- apnContext.getDataConnection().setRefCount(0);
- }
apnContext.setState(State.IDLE);
mPhone.notifyDataConnection(apnContext.getReason(), apnContext.getApnType());
apnContext.setDataConnection(null);
@@ -946,7 +939,12 @@
dc = (GsmDataConnection)apnContext.getDataConnection();
if (dc == null) {
- dc = findReadyDataConnection(apn);
+
+ dc = (GsmDataConnection) checkForConnectionForApnContext(apnContext);
+
+ if (dc == null) {
+ dc = findReadyDataConnection(apn);
+ }
if (dc == null) {
if (DBG) log("setupData: No ready GsmDataConnection found!");
@@ -964,16 +962,16 @@
return false;
}
+ DataConnectionAc dcac = mDataConnectionAsyncChannels.get(dc.getDataConnectionId());
dc.setProfileId( profileId );
dc.setActiveApnType(apnContext.getApnType());
- int refCount = dc.incAndGetRefCount();
+ int refCount = dcac.getRefCountSync();
if (DBG) log("setupData: init dc and apnContext refCount=" + refCount);
// configure retry count if no other Apn is using the same connection.
- if (refCount == 1) {
+ if (refCount == 0) {
configureRetry(dc, apnContext.getApnType());
}
- DataConnectionAc dcac = mDataConnectionAsyncChannels.get(dc.getDataConnectionId());
apnContext.setDataConnectionAc(mDataConnectionAsyncChannels.get(dc.getDataConnectionId()));
apnContext.setApnSetting(apn);
apnContext.setDataConnection(dc);
@@ -1406,23 +1404,7 @@
}
int nextReconnectDelay = apnContext.getDataConnection().getRetryTimer();
- if (DBG) {
- log("reconnectAfterFail: activate failed. Scheduling next attempt for "
- + (nextReconnectDelay / 1000) + "s");
- }
-
- AlarmManager am =
- (AlarmManager) mPhone.getContext().getSystemService(Context.ALARM_SERVICE);
- Intent intent = new Intent(INTENT_RECONNECT_ALARM);
- intent.putExtra(INTENT_RECONNECT_ALARM_EXTRA_REASON, apnContext.getReason());
- // Should put an extra of apn type?
- intent.putExtra(INTENT_RECONNECT_ALARM_EXTRA_TYPE, apnContext.getApnType());
- apnContext.setReconnectIntent(PendingIntent.getBroadcast (
- mPhone.getContext(), 0, intent, 0));
- am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
- SystemClock.elapsedRealtime() + nextReconnectDelay,
- apnContext.getReconnectIntent());
-
+ startAlarmForReconnect(nextReconnectDelay, apnContext);
apnContext.getDataConnection().increaseRetryCount();
if (!shouldPostNotification(lastFailCauseCode)) {
@@ -1436,6 +1418,25 @@
}
}
+ private void startAlarmForReconnect(int delay, ApnContext apnContext) {
+
+ if (DBG) {
+ log("Schedule alarm for reconnect: activate failed. Scheduling next attempt for "
+ + (delay / 1000) + "s");
+ }
+
+ AlarmManager am =
+ (AlarmManager) mPhone.getContext().getSystemService(Context.ALARM_SERVICE);
+ Intent intent = new Intent(INTENT_RECONNECT_ALARM);
+ intent.putExtra(INTENT_RECONNECT_ALARM_EXTRA_REASON, apnContext.getReason());
+ intent.putExtra(INTENT_RECONNECT_ALARM_EXTRA_TYPE, apnContext.getApnType());
+ apnContext.setReconnectIntent(PendingIntent.getBroadcast (
+ mPhone.getContext(), 0, intent, 0));
+ am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ SystemClock.elapsedRealtime() + delay, apnContext.getReconnectIntent());
+
+ }
+
private void notifyNoData(GsmDataConnection.FailCause lastFailCauseCode,
ApnContext apnContext) {
if (DBG) log( "notifyNoData: type=" + apnContext.getApnType());
@@ -1490,23 +1491,10 @@
} else {
apnContext.setReason(Phone.REASON_DATA_ENABLED);
}
- DataConnection conn = checkForConnectionForApnContext(apnContext);
- if (conn == null) {
- if (apnContext.getState() == State.FAILED) {
- apnContext.setState(State.IDLE);
- }
- trySetup = true;
- } else {
- int refCount = conn.incAndGetRefCount();
- apnContext.setDataConnection(conn);
- apnContext.setDataConnectionAc(
- mDataConnectionAsyncChannels.get(conn.getDataConnectionId()));
- if (DBG) {
- log("applyNewState: Found existing connection for " +
- apnContext.getApnType() + " inc refCount=" + refCount +
- " conn=" + conn);
- }
+ if (apnContext.getState() == State.FAILED) {
+ apnContext.setState(State.IDLE);
}
+ trySetup = true;
}
}
apnContext.setEnabled(enabled);
@@ -1641,7 +1629,7 @@
if (DBG) {
log(String.format("onDataSetupComplete: success apn=%s",
- apnContext.getWaitingApns().get(0).apn) + " refCount=" + dc.getRefCount());
+ apnContext.getWaitingApns().get(0).apn));
}
ApnSetting apn = apnContext.getApnSetting();
if (apn.proxy != null && apn.proxy.length() != 0) {
@@ -1709,10 +1697,9 @@
apnContext.setState(State.FAILED);
mPhone.notifyDataConnection(Phone.REASON_APN_FAILED, apnContext.getApnType());
- int refCount = releaseApnContext(apnContext, false);
+ releaseApnContext(apnContext, false);
if (DBG) {
- log("onDataSetupComplete: permanent error apn=%s" + apnString +
- " refCount=" + refCount);
+ log("onDataSetupComplete: permanent error apn=%s" + apnString );
}
} else {
if (DBG) log("onDataSetupComplete: Not all permanent failures, retry");
@@ -1723,8 +1710,7 @@
apnContext.setState(State.SCANNING);
// Wait a bit before trying the next APN, so that
// we're not tying up the RIL command channel
- sendMessageDelayed(obtainMessage(EVENT_TRY_SETUP_DATA, apnContext),
- APN_DELAY_MILLIS);
+ startAlarmForReconnect(APN_DELAY_MILLIS, apnContext);
}
}
}
@@ -1764,7 +1750,7 @@
// Wait a bit before trying the next APN, so that
// we're not tying up the RIL command channel.
// This also helps in any external dependency to turn off the context.
- sendMessageDelayed(obtainMessage(EVENT_TRY_SETUP_DATA, apnContext),APN_DELAY_MILLIS);
+ startAlarmForReconnect(APN_DELAY_MILLIS, apnContext);
}
}
@@ -1807,7 +1793,10 @@
protected void onCleanUpConnection(boolean tearDown, int apnId, String reason) {
if (DBG) log("onCleanUpConnection");
ApnContext apnContext = mApnContexts.get(apnIdToType(apnId));
- cleanUpConnection(tearDown, apnContext);
+ if (apnContext != null) {
+ apnContext.setReason(reason);
+ cleanUpConnection(tearDown, apnContext);
+ }
}
protected boolean isConnected() {
diff --git a/tests/DumpRenderTree2/assets/run_apache2.py b/tests/DumpRenderTree2/assets/run_apache2.py
index b4a8685..3806599 100755
--- a/tests/DumpRenderTree2/assets/run_apache2.py
+++ b/tests/DumpRenderTree2/assets/run_apache2.py
@@ -79,8 +79,8 @@
# complete set of tests and the required scripts.
directives += " -c \"DocumentRoot " + os.path.join(layout_tests_path, "http", "tests/") + "\""
directives += " -c \"Alias /LayoutTests " + layout_tests_path + "\""
- directives += " -c \"Alias /WebKitTools/DumpRenderTree/android " + \
- os.path.join(webkit_path, "WebKitTools", "DumpRenderTree", "android") + "\""
+ directives += " -c \"Alias /Tools/DumpRenderTree/android " + \
+ os.path.join(webkit_path, "Tools", "DumpRenderTree", "android") + "\""
directives += " -c \"Alias /ThirdPartyProject.prop " + \
os.path.join(webkit_path, "ThirdPartyProject.prop") + "\""
diff --git a/tests/DumpRenderTree2/assets/run_layout_tests.py b/tests/DumpRenderTree2/assets/run_layout_tests.py
index 3b8c09a..161416a 100755
--- a/tests/DumpRenderTree2/assets/run_layout_tests.py
+++ b/tests/DumpRenderTree2/assets/run_layout_tests.py
@@ -44,9 +44,15 @@
logging.info("Running the tests...")
logging.debug("Command = %s" % cmd)
(stdoutdata, stderrdata) = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
+ if stderrdata != "":
+ logging.info("Failed to start tests:\n%s", stderrdata)
+ return
if re.search("^INSTRUMENTATION_STATUS_CODE: -1", stdoutdata, re.MULTILINE) != None:
logging.info("Failed to run the tests. Is DumpRenderTree2 installed on the device?")
return
+ if re.search("^OK \([0-9]+ tests?\)", stdoutdata, re.MULTILINE) == None:
+ logging.info("DumpRenderTree2 failed to run correctly:\n%s", stdoutdata)
+ return
logging.info("Downloading the summaries...")
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/FsUtils.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/FsUtils.java
index d1aba437..54cbfda 100644
--- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/FsUtils.java
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/FsUtils.java
@@ -63,7 +63,7 @@
public static final String LOG_TAG = "FsUtils";
private static final String SCRIPT_URL = ForwarderManager.getHostSchemePort(false) +
- "WebKitTools/DumpRenderTree/android/get_layout_tests_dir_contents.php";
+ "Tools/DumpRenderTree/android/get_layout_tests_dir_contents.php";
private static final int HTTP_TIMEOUT_MS = 5000;
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsExecutor.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsExecutor.java
index d9f5dd4..f59da37 100644
--- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsExecutor.java
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsExecutor.java
@@ -398,6 +398,11 @@
}
private void startTests() {
+ // This is called when the tests are started and after each crash.
+ // We only send the reset message in the former case.
+ if (mCurrentTestIndex <= 0) {
+ sendResetMessage();
+ }
if (mCurrentTestIndex == 0) {
sendFirstTestMessage();
}
@@ -405,6 +410,15 @@
runNextTest();
}
+ private void sendResetMessage() {
+ try {
+ Message serviceMsg = Message.obtain(null, ManagerService.MSG_RESET);
+ mManagerServiceMessenger.send(serviceMsg);
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "Error sending message to manager service:", e);
+ }
+ }
+
private void sendFirstTestMessage() {
try {
Message serviceMsg = Message.obtain(null, ManagerService.MSG_FIRST_TEST);
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/ManagerService.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/ManagerService.java
index e4df62d..4783cc7 100644
--- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/ManagerService.java
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/ManagerService.java
@@ -66,6 +66,7 @@
static final int MSG_ALL_TESTS_FINISHED = 1;
static final int MSG_FIRST_TEST = 2;
static final int MSG_CURRENT_TEST_CRASHED = 3;
+ static final int MSG_RESET = 4;
/**
* This handler is purely for IPC. It is used to create mMessenger
@@ -75,8 +76,11 @@
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
- case MSG_FIRST_TEST:
+ case MSG_RESET:
mSummarizer.reset();
+ break;
+
+ case MSG_FIRST_TEST:
Bundle bundle = msg.getData();
ensureNextTestSetup(bundle.getString("firstTest"), bundle.getInt("index"));
break;
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/Summarizer.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/Summarizer.java
index 65c6964..bae8e6b 100644
--- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/Summarizer.java
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/Summarizer.java
@@ -568,7 +568,7 @@
URL url = null;
try {
url = new URL("http", "localhost", ForwarderManager.HTTP_PORT,
- "/WebKitTools/DumpRenderTree/android/view_source.php?src=" +
+ "/Tools/DumpRenderTree/android/view_source.php?src=" +
relativePath);
} catch (MalformedURLException e) {
assert false : "relativePath=" + relativePath;
diff --git a/tools/aapt/AaptAssets.cpp b/tools/aapt/AaptAssets.cpp
index 75535f8..4894196 100644
--- a/tools/aapt/AaptAssets.cpp
+++ b/tools/aapt/AaptAssets.cpp
@@ -913,6 +913,11 @@
(out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
| ResTable_config::UI_MODE_TYPE_CAR;
return true;
+ } else if (strcmp(name, "television") == 0) {
+ if (out) out->uiMode =
+ (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
+ | ResTable_config::UI_MODE_TYPE_TELEVISION;
+ return true;
}
return false;
diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java
index 134f29f..bd6da64 100644
--- a/wifi/java/android/net/wifi/WifiStateMachine.java
+++ b/wifi/java/android/net/wifi/WifiStateMachine.java
@@ -491,7 +491,7 @@
mNetworkInfo.setIsAvailable(false);
mLinkProperties.clear();
mLastBssid = null;
- mLastNetworkId = -1;
+ mLastNetworkId = WifiConfiguration.INVALID_NETWORK_ID;
mLastSignalLevel = -1;
mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
@@ -837,11 +837,12 @@
}
public void connectNetwork(WifiConfiguration wifiConfig) {
- /* arg1 is used to indicate netId, force a netId value of -1 when
- * we are passing a configuration since the default value of
- * 0 is a valid netId
+ /* arg1 is used to indicate netId, force a netId value of
+ * WifiConfiguration.INVALID_NETWORK_ID when we are passing
+ * a configuration since the default value of 0 is a valid netId
*/
- sendMessage(obtainMessage(CMD_CONNECT_NETWORK, -1, 0, wifiConfig));
+ sendMessage(obtainMessage(CMD_CONNECT_NETWORK, WifiConfiguration.INVALID_NETWORK_ID,
+ 0, wifiConfig));
}
public void saveNetwork(WifiConfiguration wifiConfig) {
@@ -1445,7 +1446,7 @@
mWifiInfo.setInetAddress(null);
mWifiInfo.setBSSID(null);
mWifiInfo.setSSID(null);
- mWifiInfo.setNetworkId(-1);
+ mWifiInfo.setNetworkId(WifiConfiguration.INVALID_NETWORK_ID);
mWifiInfo.setRssi(MIN_RSSI);
mWifiInfo.setLinkSpeed(-1);
@@ -1457,7 +1458,7 @@
mLinkProperties.clear();
mLastBssid= null;
- mLastNetworkId = -1;
+ mLastNetworkId = WifiConfiguration.INVALID_NETWORK_ID;
}
@@ -2050,7 +2051,7 @@
mWpsStateMachine.sendMessage(CMD_RESET_WPS_STATE);
/* Initialize data structures */
mLastBssid = null;
- mLastNetworkId = -1;
+ mLastNetworkId = WifiConfiguration.INVALID_NETWORK_ID;
mLastSignalLevel = -1;
mWifiInfo.setMacAddress(WifiNative.getMacAddressCommand());
@@ -2545,7 +2546,10 @@
// Network id is only valid when we start connecting
if (SupplicantState.isConnecting(state)) {
mWifiInfo.setNetworkId(stateChangeResult.networkId);
+ } else {
+ mWifiInfo.setNetworkId(WifiConfiguration.INVALID_NETWORK_ID);
}
+
if (state == SupplicantState.ASSOCIATING) {
/* BSSID is valid only in ASSOCIATING state */
mWifiInfo.setBSSID(stateChangeResult.BSSID);