Merge "Prevent scaling beyond zoom overview scale."
diff --git a/api/current.xml b/api/current.xml
index f02ec65..708a852 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -20161,17 +20161,6 @@
visibility="public"
>
</method>
-<method name="getGetter"
- return="java.lang.reflect.Method"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
<method name="getPropertyName"
return="java.lang.String"
abstract="false"
@@ -20183,17 +20172,6 @@
visibility="public"
>
</method>
-<method name="getSetter"
- return="java.lang.reflect.Method"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
<method name="ofFloat"
return="android.animation.PropertyValuesHolder"
abstract="false"
@@ -20282,19 +20260,6 @@
<parameter name="values" type="float...">
</parameter>
</method>
-<method name="setGetter"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="getter" type="java.lang.reflect.Method">
-</parameter>
-</method>
<method name="setIntValues"
return="void"
abstract="false"
@@ -20347,19 +20312,6 @@
<parameter name="propertyName" type="java.lang.String">
</parameter>
</method>
-<method name="setSetter"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="setter" type="java.lang.reflect.Method">
-</parameter>
-</method>
</class>
<class name="RGBEvaluator"
extends="java.lang.Object"
@@ -250407,7 +250359,7 @@
deprecated="not deprecated"
visibility="public"
>
-<parameter name="arg0" type="T">
+<parameter name="t" type="T">
</parameter>
</method>
</interface>
diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java
index 22e04a7..5fe3644 100644
--- a/core/java/android/animation/Animator.java
+++ b/core/java/android/animation/Animator.java
@@ -36,22 +36,36 @@
* this call, because all animation events are posted to a central timing loop so that animation
* times are all synchronized on a single timing pulse on the UI thread. So the animation will
* start the next time that event handler processes events.
+ *
+ * <p>The animation started by calling this method will be run on the thread that called
+ * this method. This thread should have a Looper on it (a runtime exception will be thrown if
+ * this is not the case). Also, if the animation will animate
+ * properties of objects in the view hierarchy, then the calling thread should be the UI
+ * thread for that view hierarchy.</p>
+ *
*/
public void start() {
}
/**
* Cancels the animation. Unlike {@link #end()}, <code>cancel()</code> causes the animation to
- * stop in its tracks, sending an {@link android.animation.Animator.AnimatorListener#onAnimationCancel(Animator)} to
- * its listeners, followed by an {@link android.animation.Animator.AnimatorListener#onAnimationEnd(Animator)} message.
+ * stop in its tracks, sending an
+ * {@link android.animation.Animator.AnimatorListener#onAnimationCancel(Animator)} to
+ * its listeners, followed by an
+ * {@link android.animation.Animator.AnimatorListener#onAnimationEnd(Animator)} message.
+ *
+ * <p>This method must be called on the thread that is running the animation.</p>
*/
public void cancel() {
}
/**
* Ends the animation. This causes the animation to assign the end value of the property being
- * animated, then calling the {@link android.animation.Animator.AnimatorListener#onAnimationEnd(Animator)} method on
+ * animated, then calling the
+ * {@link android.animation.Animator.AnimatorListener#onAnimationEnd(Animator)} method on
* its listeners.
+ *
+ * <p>This method must be called on the thread that is running the animation.</p>
*/
public void end() {
}
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index 154e084..d77dbdc 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -96,6 +96,9 @@
// The amount of time in ms to delay starting the animation after start() is called
private long mStartDelay = 0;
+ // Animator used for a nonzero startDelay
+ private ValueAnimator mDelayAnim = null;
+
// How long the child animations should last in ms. The default value is negative, which
// simply means that there is no duration set on the AnimatorSet. When a real duration is
@@ -276,6 +279,19 @@
listener.onAnimationCancel(this);
}
}
+ if (mDelayAnim != null && mDelayAnim.isRunning()) {
+ // If we're currently in the startDelay period, just cancel that animator and
+ // send out the end event to all listeners
+ mDelayAnim.cancel();
+ if (mListeners != null) {
+ ArrayList<AnimatorListener> tmpListeners =
+ (ArrayList<AnimatorListener>) mListeners.clone();
+ for (AnimatorListener listener : tmpListeners) {
+ listener.onAnimationEnd(this);
+ }
+ }
+ return;
+ }
if (mSortedNodes.size() > 0) {
for (Node node : mSortedNodes) {
node.animation.cancel();
@@ -302,6 +318,9 @@
node.animation.addListener(mSetListener);
}
}
+ if (mDelayAnim != null) {
+ mDelayAnim.cancel();
+ }
if (mSortedNodes.size() > 0) {
for (Node node : mSortedNodes) {
node.animation.end();
@@ -411,12 +430,25 @@
// contains the animation nodes in the correct order.
sortNodes();
+ int numSortedNodes = mSortedNodes.size();
+ for (int i = 0; i < numSortedNodes; ++i) {
+ Node node = mSortedNodes.get(i);
+ // First, clear out the old listeners
+ ArrayList<AnimatorListener> oldListeners = node.animation.getListeners();
+ if (oldListeners != null && oldListeners.size() > 0) {
+ for (AnimatorListener listener : oldListeners) {
+ if (listener instanceof DependencyListener) {
+ node.animation.removeListener(listener);
+ }
+ }
+ }
+ }
+
// nodesToStart holds the list of nodes to be started immediately. We don't want to
// start the animations in the loop directly because we first need to set up
// dependencies on all of the nodes. For example, we don't want to start an animation
// when some other animation also wants to start when the first animation begins.
final ArrayList<Node> nodesToStart = new ArrayList<Node>();
- int numSortedNodes = mSortedNodes.size();
for (int i = 0; i < numSortedNodes; ++i) {
Node node = mSortedNodes.get(i);
if (mSetListener == null) {
@@ -443,19 +475,25 @@
}
} else {
// TODO: Need to cancel out of the delay appropriately
- ValueAnimator delayAnim = ValueAnimator.ofFloat(0f, 1f);
- delayAnim.setDuration(mStartDelay);
- delayAnim.addListener(new AnimatorListenerAdapter() {
+ mDelayAnim = ValueAnimator.ofFloat(0f, 1f);
+ mDelayAnim.setDuration(mStartDelay);
+ mDelayAnim.addListener(new AnimatorListenerAdapter() {
+ boolean canceled = false;
+ public void onAnimationCancel(Animator anim) {
+ canceled = true;
+ }
public void onAnimationEnd(Animator anim) {
- int numNodes = nodesToStart.size();
- for (int i = 0; i < numNodes; ++i) {
- Node node = nodesToStart.get(i);
- node.animation.start();
- mPlayingSet.add(node.animation);
+ if (!canceled) {
+ int numNodes = nodesToStart.size();
+ for (int i = 0; i < numNodes; ++i) {
+ Node node = nodesToStart.get(i);
+ node.animation.start();
+ mPlayingSet.add(node.animation);
+ }
}
}
});
- delayAnim.start();
+ mDelayAnim.start();
}
if (mListeners != null) {
ArrayList<AnimatorListener> tmpListeners =
diff --git a/core/java/android/animation/ObjectAnimator.java b/core/java/android/animation/ObjectAnimator.java
index 7c2e70d..7f11871 100644
--- a/core/java/android/animation/ObjectAnimator.java
+++ b/core/java/android/animation/ObjectAnimator.java
@@ -19,6 +19,7 @@
import android.util.Log;
import java.lang.reflect.Method;
+import java.util.ArrayList;
/**
* This subclass of {@link ValueAnimator} provides support for animating properties on target objects.
@@ -31,6 +32,7 @@
*
*/
public final class ObjectAnimator extends ValueAnimator {
+ private static final boolean DBG = false;
// The target object on which the property exists, set in the constructor
private Object mTarget;
@@ -265,6 +267,21 @@
}
}
+ @Override
+ public void start() {
+ if (DBG) {
+ Log.d("ObjectAnimator", "Anim target, duration" + mTarget + ", " + getDuration());
+ for (int i = 0; i < mValues.length; ++i) {
+ PropertyValuesHolder pvh = mValues[i];
+ ArrayList<Keyframe> keyframes = pvh.mKeyframeSet.mKeyframes;
+ Log.d("ObjectAnimator", " Values[" + i + "]: " +
+ pvh.getPropertyName() + ", " + keyframes.get(0).getValue() + ", " +
+ keyframes.get(pvh.mKeyframeSet.mNumKeyframes - 1).getValue());
+ }
+ }
+ super.start();
+ }
+
/**
* This function is called immediately before processing the first animation
* frame of an animation. If there is a nonzero <code>startDelay</code>, the
diff --git a/core/java/android/animation/PropertyValuesHolder.java b/core/java/android/animation/PropertyValuesHolder.java
index 97aa5a1..286c03b 100644
--- a/core/java/android/animation/PropertyValuesHolder.java
+++ b/core/java/android/animation/PropertyValuesHolder.java
@@ -544,67 +544,6 @@
}
/**
- * Sets the <code>Method</code> that is called with the animated values calculated
- * during the animation. Setting the setter method is an alternative to supplying a
- * {@link #setPropertyName(String) propertyName} from which the method is derived. This
- * approach is more direct, and is especially useful when a function must be called that does
- * not correspond to the convention of <code>setName()</code>. For example, if a function
- * called <code>offset()</code> is to be called with the animated values, there is no way
- * to tell <code>ObjectAnimator</code> how to call that function simply through a property
- * name, so a setter method should be supplied instead.
- *
- * <p>Note that the setter function must take the same parameter type as the
- * <code>valueFrom</code> and <code>valueTo</code> properties, otherwise the call to
- * the setter function will fail.</p>
- *
- * @param setter The setter method that should be called with the animated values.
- */
- public void setSetter(Method setter) {
- mSetter = setter;
- }
-
- /**
- * Gets the <code>Method</code> that is called with the animated values calculated
- * during the animation.
- */
- public Method getSetter() {
- return mSetter;
- }
-
- /**
- * Sets the <code>Method</code> that is called to get unsupplied <code>valueFrom</code> or
- * <code>valueTo</code> properties. Setting the getter method is an alternative to supplying a
- * {@link #setPropertyName(String) propertyName} from which the method is derived. This
- * approach is more direct, and is especially useful when a function must be called that does
- * not correspond to the convention of <code>setName()</code>. For example, if a function
- * called <code>offset()</code> is to be called to get an initial value, there is no way
- * to tell <code>ObjectAnimator</code> how to call that function simply through a property
- * name, so a getter method should be supplied instead.
- *
- * <p>Note that the getter method is only called whether supplied here or derived
- * from the property name, if one of <code>valueFrom</code> or <code>valueTo</code> are
- * null. If both of those values are non-null, then there is no need to get one of the
- * values and the getter is not called.
- *
- * <p>Note that the getter function must return the same parameter type as the
- * <code>valueFrom</code> and <code>valueTo</code> properties (whichever of them are
- * non-null), otherwise the call to the getter function will fail.</p>
- *
- * @param getter The getter method that should be called to get initial animation values.
- */
- public void setGetter(Method getter) {
- mGetter = getter;
- }
-
- /**
- * Gets the <code>Method</code> that is called to get unsupplied <code>valueFrom</code> or
- * <code>valueTo</code> properties.
- */
- public Method getGetter() {
- return mGetter;
- }
-
- /**
* Sets the name of the property that will be animated. This name is used to derive
* a setter function that will be called to set animated values.
* For example, a property name of <code>foo</code> will result
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index 8e541c2..1542c49 100755
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -62,9 +62,7 @@
*/
private static final int STOPPED = 0; // Not yet playing
private static final int RUNNING = 1; // Playing normally
- private static final int CANCELED = 2; // cancel() called - need to end it
- private static final int ENDED = 3; // end() called - need to end it
- private static final int SEEKED = 4; // Seeked to some time value
+ private static final int SEEKED = 2; // Seeked to some time value
/**
* Internal variables
@@ -178,7 +176,7 @@
* Flag that represents the current state of the animation. Used to figure out when to start
* an animation (if state == STOPPED). Also used to end an animation that
* has been cancel()'d or end()'d since the last animation frame. Possible values are
- * STOPPED, RUNNING, ENDED, CANCELED.
+ * STOPPED, RUNNING, SEEKED.
*/
private int mPlayingState = STOPPED;
@@ -581,8 +579,7 @@
for (int i = 0; i < count; ++i) {
ValueAnimator anim = pendingCopy.get(i);
// If the animation has a startDelay, place it on the delayed list
- if (anim.mStartDelay == 0 || anim.mPlayingState == ENDED ||
- anim.mPlayingState == CANCELED) {
+ if (anim.mStartDelay == 0) {
anim.startAnimation();
} else {
delayedAnims.add(anim);
@@ -619,14 +616,28 @@
// Now process all active animations. The return value from animationFrame()
// tells the handler whether it should now be ended
int numAnims = animations.size();
- for (int i = 0; i < numAnims; ++i) {
+ int i = 0;
+ while (i < numAnims) {
ValueAnimator anim = animations.get(i);
if (anim.animationFrame(currentTime)) {
endingAnims.add(anim);
}
+ if (animations.size() == numAnims) {
+ ++i;
+ } else {
+ // An animation might be canceled or ended by client code
+ // during the animation frame. Check to see if this happened by
+ // seeing whether the current index is the same as it was before
+ // calling animationFrame(). Another approach would be to copy
+ // animations to a temporary list and process that list instead,
+ // but that entails garbage and processing overhead that would
+ // be nice to avoid.
+ --numAnims;
+ endingAnims.remove(anim);
+ }
}
if (endingAnims.size() > 0) {
- for (int i = 0; i < endingAnims.size(); ++i) {
+ for (i = 0; i < endingAnims.size(); ++i) {
endingAnims.get(i).endAnimation();
}
endingAnims.clear();
@@ -918,9 +929,7 @@
// to run
if (mPlayingState != STOPPED || sPendingAnimations.get().contains(this) ||
sDelayedAnims.get().contains(this)) {
- // Just set the CANCELED flag - this causes the animation to end the next time a frame
- // is processed.
- mPlayingState = CANCELED;
+ endAnimation();
}
}
@@ -929,23 +938,21 @@
if (!sAnimations.get().contains(this) && !sPendingAnimations.get().contains(this)) {
// Special case if the animation has not yet started; get it ready for ending
mStartedDelay = false;
- sPendingAnimations.get().add(this);
- AnimationHandler animationHandler = sAnimationHandler.get();
- if (animationHandler == null) {
- animationHandler = new AnimationHandler();
- sAnimationHandler.set(animationHandler);
- }
- animationHandler.sendEmptyMessage(ANIMATION_START);
+ startAnimation();
}
- // Just set the ENDED flag - this causes the animation to end the next time a frame
- // is processed.
- mPlayingState = ENDED;
+ // The final value set on the target varies, depending on whether the animation
+ // was supposed to repeat an odd number of times
+ if (mRepeatCount > 0 && (mRepeatCount & 0x01) == 1) {
+ animateValue(0f);
+ } else {
+ animateValue(1f);
+ }
+ endAnimation();
}
@Override
public boolean isRunning() {
- // ENDED or CANCELED indicate that it has been ended or canceled, but not processed yet
- return (mPlayingState == RUNNING || mPlayingState == ENDED || mPlayingState == CANCELED);
+ return (mPlayingState == RUNNING);
}
/**
@@ -973,6 +980,8 @@
*/
private void endAnimation() {
sAnimations.get().remove(this);
+ sPendingAnimations.get().remove(this);
+ sDelayedAnims.get().remove(this);
mPlayingState = STOPPED;
if (mListeners != null) {
ArrayList<AnimatorListener> tmpListeners =
@@ -1014,10 +1023,6 @@
* should be added to the set of active animations.
*/
private boolean delayedAnimationFrame(long currentTime) {
- if (mPlayingState == CANCELED || mPlayingState == ENDED) {
- // end the delay, process an animation frame to actually cancel it
- return true;
- }
if (!mStartedDelay) {
mStartedDelay = true;
mDelayStartTime = currentTime;
@@ -1088,19 +1093,6 @@
}
animateValue(fraction);
break;
- case ENDED:
- // The final value set on the target varies, depending on whether the animation
- // was supposed to repeat an odd number of times
- if (mRepeatCount > 0 && (mRepeatCount & 0x01) == 1) {
- animateValue(0f);
- } else {
- animateValue(1f);
- }
- // Fall through to set done flag
- case CANCELED:
- done = true;
- mPlayingState = STOPPED;
- break;
}
return done;
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index d2de382..f1842c6 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -532,10 +532,6 @@
throw new RuntimeException("Not supported in system context");
}
- static File makeBackupFile(File prefsFile) {
- return new File(prefsFile.getPath() + ".bak");
- }
-
public File getSharedPrefsFile(String name) {
return makeFilename(getPreferencesDir(), name + ".xml");
}
@@ -543,54 +539,19 @@
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
SharedPreferencesImpl sp;
- File prefsFile;
- boolean needInitialLoad = false;
synchronized (sSharedPrefs) {
sp = sSharedPrefs.get(name);
- if (sp != null && !sp.hasFileChangedUnexpectedly()) {
- return sp;
- }
- prefsFile = getSharedPrefsFile(name);
if (sp == null) {
- sp = new SharedPreferencesImpl(prefsFile, mode, null);
+ File prefsFile = getSharedPrefsFile(name);
+ sp = new SharedPreferencesImpl(prefsFile, mode);
sSharedPrefs.put(name, sp);
- needInitialLoad = true;
- }
- }
-
- synchronized (sp) {
- if (needInitialLoad && sp.isLoaded()) {
- // lost the race to load; another thread handled it
return sp;
}
- File backup = makeBackupFile(prefsFile);
- if (backup.exists()) {
- prefsFile.delete();
- backup.renameTo(prefsFile);
- }
-
- // Debugging
- if (prefsFile.exists() && !prefsFile.canRead()) {
- Log.w(TAG, "Attempt to read preferences file " + prefsFile + " without permission");
- }
-
- Map map = null;
- FileStatus stat = new FileStatus();
- if (FileUtils.getFileStatus(prefsFile.getPath(), stat) && prefsFile.canRead()) {
- try {
- FileInputStream str = new FileInputStream(prefsFile);
- map = XmlUtils.readMapXml(str);
- str.close();
- } catch (XmlPullParserException e) {
- Log.w(TAG, "getSharedPreferences", e);
- } catch (FileNotFoundException e) {
- Log.w(TAG, "getSharedPreferences", e);
- } catch (IOException e) {
- Log.w(TAG, "getSharedPreferences", e);
- }
- }
- sp.replace(map, stat);
}
+ // If somebody else (some other process) changed the prefs
+ // file behind our back, we reload it. This has been the
+ // historical (if undocumented) behavior.
+ sp.startReloadIfChangedUnexpectedly();
return sp;
}
diff --git a/core/java/android/app/SharedPreferencesImpl.java b/core/java/android/app/SharedPreferencesImpl.java
index a807d3b..8aee65c 100644
--- a/core/java/android/app/SharedPreferencesImpl.java
+++ b/core/java/android/app/SharedPreferencesImpl.java
@@ -25,9 +25,12 @@
import com.google.android.collect.Maps;
import com.android.internal.util.XmlUtils;
+import dalvik.system.BlockGuard;
+
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
+import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -61,32 +64,88 @@
private final Object mWritingToDiskLock = new Object();
private static final Object mContent = new Object();
- private final WeakHashMap<OnSharedPreferenceChangeListener, Object> mListeners;
+ private final WeakHashMap<OnSharedPreferenceChangeListener, Object> mListeners =
+ new WeakHashMap<OnSharedPreferenceChangeListener, Object>();
- SharedPreferencesImpl(
- File file, int mode, Map initialContents) {
+ SharedPreferencesImpl(File file, int mode) {
mFile = file;
- mBackupFile = ContextImpl.makeBackupFile(file);
+ mBackupFile = makeBackupFile(file);
mMode = mode;
- mLoaded = initialContents != null;
- mMap = initialContents != null ? initialContents : new HashMap<String, Object>();
- FileStatus stat = new FileStatus();
- if (FileUtils.getFileStatus(file.getPath(), stat)) {
- mStatTimestamp = stat.mtime;
- }
- mListeners = new WeakHashMap<OnSharedPreferenceChangeListener, Object>();
+ mLoaded = false;
+ mMap = null;
+ startLoadFromDisk();
}
- // Has this SharedPreferences ever had values assigned to it?
- boolean isLoaded() {
+ private void startLoadFromDisk() {
synchronized (this) {
- return mLoaded;
+ mLoaded = false;
+ }
+ new Thread("SharedPreferencesImpl-load") {
+ public void run() {
+ synchronized (SharedPreferencesImpl.this) {
+ loadFromDiskLocked();
+ }
+ }
+ }.start();
+ }
+
+ private void loadFromDiskLocked() {
+ if (mLoaded) {
+ return;
+ }
+ if (mBackupFile.exists()) {
+ mFile.delete();
+ mBackupFile.renameTo(mFile);
+ }
+
+ // Debugging
+ if (mFile.exists() && !mFile.canRead()) {
+ Log.w(TAG, "Attempt to read preferences file " + mFile + " without permission");
+ }
+
+ Map map = null;
+ FileStatus stat = new FileStatus();
+ if (FileUtils.getFileStatus(mFile.getPath(), stat) && mFile.canRead()) {
+ try {
+ FileInputStream str = new FileInputStream(mFile);
+ map = XmlUtils.readMapXml(str);
+ str.close();
+ } catch (XmlPullParserException e) {
+ Log.w(TAG, "getSharedPreferences", e);
+ } catch (FileNotFoundException e) {
+ Log.w(TAG, "getSharedPreferences", e);
+ } catch (IOException e) {
+ Log.w(TAG, "getSharedPreferences", e);
+ }
+ }
+ mLoaded = true;
+ if (map != null) {
+ mMap = map;
+ mStatTimestamp = stat.mtime;
+ mStatSize = stat.size;
+ } else {
+ mMap = new HashMap<String, Object>();
+ }
+ notifyAll();
+ }
+
+ private static File makeBackupFile(File prefsFile) {
+ return new File(prefsFile.getPath() + ".bak");
+ }
+
+ void startReloadIfChangedUnexpectedly() {
+ synchronized (this) {
+ // TODO: wait for any pending writes to disk?
+ if (!hasFileChangedUnexpectedly()) {
+ return;
+ }
+ startLoadFromDisk();
}
}
// Has the file changed out from under us? i.e. writes that
// we didn't instigate.
- public boolean hasFileChangedUnexpectedly() {
+ private boolean hasFileChangedUnexpectedly() {
synchronized (this) {
if (mDiskWritesInFlight > 0) {
// If we know we caused it, it's not unexpected.
@@ -103,19 +162,6 @@
}
}
- /*package*/ void replace(Map newContents, FileStatus stat) {
- synchronized (this) {
- mLoaded = true;
- if (newContents != null) {
- mMap = newContents;
- }
- if (stat != null) {
- mStatTimestamp = stat.mtime;
- mStatSize = stat.size;
- }
- }
- }
-
public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
synchronized(this) {
mListeners.put(listener, mContent);
@@ -128,8 +174,24 @@
}
}
+ private void awaitLoadedLocked() {
+ if (!mLoaded) {
+ // Raise an explicit StrictMode onReadFromDisk for this
+ // thread, since the real read will be in a different
+ // thread and otherwise ignored by StrictMode.
+ BlockGuard.getThreadPolicy().onReadFromDisk();
+ }
+ while (!mLoaded) {
+ try {
+ wait();
+ } catch (InterruptedException unused) {
+ }
+ }
+ }
+
public Map<String, ?> getAll() {
- synchronized(this) {
+ synchronized (this) {
+ awaitLoadedLocked();
//noinspection unchecked
return new HashMap<String, Object>(mMap);
}
@@ -137,6 +199,7 @@
public String getString(String key, String defValue) {
synchronized (this) {
+ awaitLoadedLocked();
String v = (String)mMap.get(key);
return v != null ? v : defValue;
}
@@ -144,6 +207,7 @@
public Set<String> getStringSet(String key, Set<String> defValues) {
synchronized (this) {
+ awaitLoadedLocked();
Set<String> v = (Set<String>) mMap.get(key);
return v != null ? v : defValues;
}
@@ -151,24 +215,28 @@
public int getInt(String key, int defValue) {
synchronized (this) {
+ awaitLoadedLocked();
Integer v = (Integer)mMap.get(key);
return v != null ? v : defValue;
}
}
public long getLong(String key, long defValue) {
synchronized (this) {
+ awaitLoadedLocked();
Long v = (Long)mMap.get(key);
return v != null ? v : defValue;
}
}
public float getFloat(String key, float defValue) {
synchronized (this) {
+ awaitLoadedLocked();
Float v = (Float)mMap.get(key);
return v != null ? v : defValue;
}
}
public boolean getBoolean(String key, boolean defValue) {
synchronized (this) {
+ awaitLoadedLocked();
Boolean v = (Boolean)mMap.get(key);
return v != null ? v : defValue;
}
@@ -176,11 +244,23 @@
public boolean contains(String key) {
synchronized (this) {
+ awaitLoadedLocked();
return mMap.containsKey(key);
}
}
public Editor edit() {
+ // TODO: remove the need to call awaitLoadedLocked() when
+ // requesting an editor. will require some work on the
+ // Editor, but then we should be able to do:
+ //
+ // context.getSharedPreferences(..).edit().putString(..).apply()
+ //
+ // ... all without blocking.
+ synchronized (this) {
+ awaitLoadedLocked();
+ }
+
return new EditorImpl();
}
diff --git a/core/java/android/net/DummyDataStateTracker.java b/core/java/android/net/DummyDataStateTracker.java
index a759865..daa1c09 100644
--- a/core/java/android/net/DummyDataStateTracker.java
+++ b/core/java/android/net/DummyDataStateTracker.java
@@ -189,6 +189,9 @@
return -1;
}
+ public void setDataEnable(boolean enabled) {
+ }
+
@Override
public String toString() {
StringBuffer sb = new StringBuffer("Dummy data state: none, dummy!");
diff --git a/core/java/android/net/MobileDataStateTracker.java b/core/java/android/net/MobileDataStateTracker.java
index 4df8db5..b3a354f 100644
--- a/core/java/android/net/MobileDataStateTracker.java
+++ b/core/java/android/net/MobileDataStateTracker.java
@@ -20,14 +20,21 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Messenger;
import android.os.RemoteException;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.ServiceManager;
+
+import com.android.internal.telephony.DataConnectionTracker;
import com.android.internal.telephony.ITelephony;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.util.AsyncChannel;
+
import android.net.NetworkInfo.DetailedState;
import android.net.NetworkInfo;
import android.net.LinkProperties;
@@ -67,6 +74,10 @@
// the other is also disconnected before we reset sockets
private boolean mIsDefaultOrHipri = false;
+ private Handler mHandler;
+ private AsyncChannel mDataConnectionTrackerAc;
+ private Messenger mMessenger;
+
/**
* Create a new MobileDataStateTracker
* @param netType the ConnectivityManager network type
@@ -107,14 +118,54 @@
mTarget = target;
mContext = context;
+ HandlerThread handlerThread = new HandlerThread("ConnectivityServiceThread");
+ handlerThread.start();
+ mHandler = new MdstHandler(handlerThread.getLooper(), this);
+
IntentFilter filter = new IntentFilter();
filter.addAction(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
filter.addAction(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED);
+ filter.addAction(DataConnectionTracker.ACTION_DATA_CONNECTION_TRACKER_MESSENGER);
mContext.registerReceiver(new MobileDataStateReceiver(), filter);
mMobileDataState = Phone.DataState.DISCONNECTED;
}
+ static class MdstHandler extends Handler {
+ private MobileDataStateTracker mMdst;
+
+ MdstHandler(Looper looper, MobileDataStateTracker mdst) {
+ super(looper);
+ mMdst = mdst;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
+ if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
+ if (DBG) {
+ mMdst.log("MdstHandler connected");
+ }
+ mMdst.mDataConnectionTrackerAc = (AsyncChannel) msg.obj;
+ } else {
+ if (DBG) {
+ mMdst.log("MdstHandler %s NOT connected error=" + msg.arg1);
+ }
+ }
+ break;
+ case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
+ mMdst.log("Disconnected from DataStateTracker");
+ mMdst.mDataConnectionTrackerAc = null;
+ break;
+ default: {
+ mMdst.log("Ignorning unknown message=" + msg);
+ break;
+ }
+ }
+ }
+ }
+
/**
* Return the IP addresses of the DNS servers available for the mobile data
* network interface.
@@ -179,7 +230,7 @@
false));
if (DBG) {
- log(mApnType + " Received state=" + state + ", old=" + mMobileDataState +
+ log("Received state=" + state + ", old=" + mMobileDataState +
", reason=" + (reason == null ? "(unspecified)" : reason));
}
if (mMobileDataState != state) {
@@ -265,10 +316,16 @@
String reason = intent.getStringExtra(Phone.FAILURE_REASON_KEY);
String apnName = intent.getStringExtra(Phone.DATA_APN_KEY);
if (DBG) {
- log(mApnType + "Received " + intent.getAction() +
+ log("Received " + intent.getAction() +
" broadcast" + reason == null ? "" : "(" + reason + ")");
}
setDetailedState(DetailedState.FAILED, reason, apnName);
+ } else if (intent.getAction().
+ equals(DataConnectionTracker.ACTION_DATA_CONNECTION_TRACKER_MESSENGER)) {
+ if (DBG) log(mApnType + " got ACTION_DATA_CONNECTION_TRACKER_MESSENGER");
+ mMessenger = intent.getParcelableExtra(DataConnectionTracker.EXTRA_MESSENGER);
+ AsyncChannel ac = new AsyncChannel();
+ ac.connect(mContext, MobileDataStateTracker.this.mHandler, mMessenger);
} else {
if (DBG) log("Broadcast received: ignore " + intent.getAction());
}
@@ -488,6 +545,20 @@
return -1;
}
+ /**
+ * @param enabled
+ */
+ public void setDataEnable(boolean enabled) {
+ try {
+ log("setDataEnable: E enabled=" + enabled);
+ mDataConnectionTrackerAc.sendMessage(DataConnectionTracker.CMD_SET_DATA_ENABLE,
+ enabled ? DataConnectionTracker.ENABLED : DataConnectionTracker.DISABLED);
+ log("setDataEnable: X enabled=" + enabled);
+ } catch (Exception e) {
+ log("setDataEnable: X mAc was null" + e);
+ }
+ }
+
@Override
public String toString() {
StringBuffer sb = new StringBuffer("Mobile data state: ");
@@ -543,7 +614,7 @@
case ConnectivityManager.TYPE_MOBILE_HIPRI:
return Phone.APN_TYPE_HIPRI;
default:
- loge("Error mapping networkType " + netType + " to apnType.");
+ sloge("Error mapping networkType " + netType + " to apnType.");
return null;
}
}
@@ -562,11 +633,15 @@
return new LinkCapabilities(mLinkCapabilities);
}
- static private void log(String s) {
- Slog.d(TAG, s);
+ private void log(String s) {
+ Slog.d(TAG, mApnType + ": " + s);
}
- static private void loge(String s) {
+ private void loge(String s) {
+ Slog.e(TAG, mApnType + ": " + s);
+ }
+
+ static private void sloge(String s) {
Slog.e(TAG, s);
}
}
diff --git a/core/java/android/net/NetworkStateTracker.java b/core/java/android/net/NetworkStateTracker.java
index 8afdcee..e378506 100644
--- a/core/java/android/net/NetworkStateTracker.java
+++ b/core/java/android/net/NetworkStateTracker.java
@@ -169,6 +169,11 @@
public int stopUsingNetworkFeature(String feature, int callingPid, int callingUid);
/**
+ * @param enabled
+ */
+ public void setDataEnable(boolean enabled);
+
+ /**
* -------------------------------------------------------------
* Storage API used by ConnectivityService for saving
* Network specific information.
@@ -204,5 +209,4 @@
* Indicate tear down requested from connectivity
*/
public void setTeardownRequested(boolean isRequested);
-
}
diff --git a/core/java/com/android/internal/widget/DrawableHolder.java b/core/java/com/android/internal/widget/DrawableHolder.java
index a528aeb..947e0f3 100644
--- a/core/java/com/android/internal/widget/DrawableHolder.java
+++ b/core/java/com/android/internal/widget/DrawableHolder.java
@@ -87,15 +87,12 @@
* @param property
*/
public void removeAnimationFor(String property) {
- ArrayList<ObjectAnimator> removalList = new ArrayList<ObjectAnimator>();
- for (ObjectAnimator currentAnim : mAnimators) {
+ ArrayList<ObjectAnimator> removalList = (ArrayList<ObjectAnimator>)mAnimators.clone();
+ for (ObjectAnimator currentAnim : removalList) {
if (property.equals(currentAnim.getPropertyName())) {
currentAnim.cancel();
- removalList.add(currentAnim);
}
}
- if (DBG) Log.v(TAG, "Remove list size: " + removalList.size());
- mAnimators.removeAll(removalList);
}
/**
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 758f9f3..9d11d87 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -387,7 +387,6 @@
* the number of different network types is not going
* to change very often.
*/
- boolean noMobileData = !getMobileDataEnabled();
for (int netType : mPriorityList) {
switch (mNetAttributes[netType].mRadio) {
case ConnectivityManager.TYPE_WIFI:
@@ -407,10 +406,6 @@
mNetTrackers[netType] = new MobileDataStateTracker(netType,
mNetAttributes[netType].mName);
mNetTrackers[netType].startMonitoring(context, mHandler);
- if (noMobileData) {
- if (DBG) log("tearing down Mobile networks due to setting");
- mNetTrackers[netType].teardown();
- }
break;
case ConnectivityManager.TYPE_DUMMY:
mNetTrackers[netType] = new DummyDataStateTracker(netType,
@@ -691,10 +686,6 @@
// TODO - move this into the MobileDataStateTracker
int usedNetworkType = networkType;
if(networkType == ConnectivityManager.TYPE_MOBILE) {
- if (!getMobileDataEnabled()) {
- if (DBG) log("requested special network with data disabled - rejected");
- return Phone.APN_TYPE_NOT_AVAILABLE;
- }
if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_MMS)) {
usedNetworkType = ConnectivityManager.TYPE_MOBILE_MMS;
} else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_SUPL)) {
@@ -985,6 +976,9 @@
* @see ConnectivityManager#getMobileDataEnabled()
*/
public boolean getMobileDataEnabled() {
+ // TODO: This detail should probably be in DataConnectionTracker's
+ // which is where we store the value and maybe make this
+ // asynchronous.
enforceAccessPermission();
boolean retVal = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.MOBILE_DATA, 1) == 1;
@@ -1004,42 +998,14 @@
}
private void handleSetMobileData(boolean enabled) {
- if (getMobileDataEnabled() == enabled) return;
-
- Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.MOBILE_DATA, enabled ? 1 : 0);
-
- if (enabled) {
- if (mNetTrackers[ConnectivityManager.TYPE_MOBILE] != null) {
- if (DBG) {
- log("starting up " + mNetTrackers[ConnectivityManager.TYPE_MOBILE]);
- }
- mNetTrackers[ConnectivityManager.TYPE_MOBILE].reconnect();
+ if (mNetTrackers[ConnectivityManager.TYPE_MOBILE] != null) {
+ if (DBG) {
+ Slog.d(TAG, mNetTrackers[ConnectivityManager.TYPE_MOBILE].toString() + enabled);
}
- } else {
- for (NetworkStateTracker nt : mNetTrackers) {
- if (nt == null) continue;
- int netType = nt.getNetworkInfo().getType();
- if (mNetAttributes[netType].mRadio == ConnectivityManager.TYPE_MOBILE) {
- if (DBG) log("tearing down " + nt);
- nt.teardown();
- }
- }
+ mNetTrackers[ConnectivityManager.TYPE_MOBILE].setDataEnable(enabled);
}
}
- private int getNumConnectedNetworks() {
- int numConnectedNets = 0;
-
- for (NetworkStateTracker nt : mNetTrackers) {
- if (nt != null && nt.getNetworkInfo().isConnected() &&
- !nt.isTeardownRequested()) {
- ++numConnectedNets;
- }
- }
- return numConnectedNets;
- }
-
private void enforceAccessPermission() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.ACCESS_NETWORK_STATE,
@@ -1159,16 +1125,9 @@
int newType = -1;
int newPriority = -1;
- boolean noMobileData = !getMobileDataEnabled();
for (int checkType=0; checkType <= ConnectivityManager.MAX_NETWORK_TYPE; checkType++) {
if (checkType == prevNetType) continue;
if (mNetAttributes[checkType] == null) continue;
- if (mNetAttributes[checkType].mRadio == ConnectivityManager.TYPE_MOBILE &&
- noMobileData) {
- loge("not failing over to mobile type " + checkType +
- " because Mobile Data Disabled");
- continue;
- }
if (mNetAttributes[checkType].isDefault()) {
/* TODO - if we have multiple nets we could use
* we may want to put more thought into which we choose
diff --git a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
index 069e1b8..737342f 100644
--- a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
@@ -30,6 +30,7 @@
import android.os.AsyncResult;
import android.os.Handler;
import android.os.Message;
+import android.os.Messenger;
import android.os.ServiceManager;
import android.preference.PreferenceManager;
import android.provider.Settings;
@@ -81,6 +82,10 @@
DORMANT
}
+ public static String ACTION_DATA_CONNECTION_TRACKER_MESSENGER =
+ "com.android.internal.telephony";
+ public static String EXTRA_MESSENGER = "EXTRA_MESSENGER";
+
/***** Event Codes *****/
protected static final int EVENT_DATA_SETUP_COMPLETE = 1;
protected static final int EVENT_RADIO_AVAILABLE = 3;
@@ -113,6 +118,8 @@
protected static final int EVENT_SET_INTERNAL_DATA_ENABLE = 37;
protected static final int EVENT_RESET_DONE = 38;
+ public static final int CMD_SET_DATA_ENABLE = 39;
+
/***** Constants *****/
protected static final int APN_INVALID_ID = -1;
@@ -123,13 +130,18 @@
protected static final int APN_HIPRI_ID = 4;
protected static final int APN_NUM_TYPES = 5;
- protected static final int DISABLED = 0;
- protected static final int ENABLED = 1;
+ public static final int DISABLED = 0;
+ public static final int ENABLED = 1;
// responds to the setInternalDataEnabled call - used internally to turn off data
// for example during emergency calls
protected boolean mInternalDataEnabled = true;
+ // responds to public (user) API to enable/disable data use
+ // independent of mInternalDataEnabled and requests for APN access
+ // persisted
+ protected boolean mDataEnabled = true;
+
protected boolean[] dataEnabled = new boolean[APN_NUM_TYPES];
protected int enabledCount = 0;
@@ -289,6 +301,9 @@
filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
+ mDataEnabled = Settings.Secure.getInt(mPhone.getContext().getContentResolver(),
+ Settings.Secure.MOBILE_DATA, 1) == 1;
+
// TODO: Why is this registering the phone as the receiver of the intent
// and not its own handler?
mPhone.getContext().registerReceiver(mIntentReceiver, filter, null, mPhone);
@@ -296,16 +311,8 @@
// This preference tells us 1) initial condition for "dataEnabled",
// and 2) whether the RIL will setup the baseband to auto-PS attach.
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mPhone.getContext());
- boolean dataEnabledSetting = true;
- try {
- dataEnabledSetting = IConnectivityManager.Stub.asInterface(ServiceManager.
- getService(Context.CONNECTIVITY_SERVICE)).getMobileDataEnabled();
- } catch (Exception e) {
- // nothing to do - use the old behavior and leave data on
- }
dataEnabled[APN_DEFAULT_ID] =
- !sp.getBoolean(PhoneBase.DATA_DISABLED_ON_BOOT_KEY, false) &&
- dataEnabledSetting;
+ !sp.getBoolean(PhoneBase.DATA_DISABLED_ON_BOOT_KEY, false);
if (dataEnabled[APN_DEFAULT_ID]) {
enabledCount++;
}
@@ -316,6 +323,12 @@
mPhone.getContext().unregisterReceiver(this.mIntentReceiver);
}
+ protected void broadcastMessenger() {
+ Intent intent = new Intent(ACTION_DATA_CONNECTION_TRACKER_MESSENGER);
+ intent.putExtra(EXTRA_MESSENGER, new Messenger(this));
+ mPhone.getContext().sendBroadcast(intent);
+ }
+
public Activity getActivity() {
return mActivity;
}
@@ -490,14 +503,20 @@
onCleanUpConnection(tearDown, (String) msg.obj);
break;
- case EVENT_SET_INTERNAL_DATA_ENABLE:
+ case EVENT_SET_INTERNAL_DATA_ENABLE: {
boolean enabled = (msg.arg1 == ENABLED) ? true : false;
onSetInternalDataEnabled(enabled);
break;
-
+ }
case EVENT_RESET_DONE:
onResetDone((AsyncResult) msg.obj);
break;
+ case CMD_SET_DATA_ENABLE: {
+ log("CMD_SET_DATA_ENABLE msg=" + msg);
+ boolean enabled = (msg.arg1 == ENABLED) ? true : false;
+ onSetDataEnabled(enabled);
+ break;
+ }
default:
Log.e("DATA", "Unidentified event = " + msg.what);
@@ -512,7 +531,7 @@
* {@code true} otherwise.
*/
public synchronized boolean getAnyDataEnabled() {
- return (mInternalDataEnabled && (enabledCount != 0));
+ return (mInternalDataEnabled && mDataEnabled && (enabledCount != 0));
}
protected abstract void startNetStatPoll();
@@ -828,11 +847,6 @@
* Prevent mobile data connections from being established, or once again
* allow mobile data connections. If the state toggles, then either tear
* down or set up data, as appropriate to match the new state.
- * <p>
- * This operation only affects the default APN, and if the same APN is
- * currently being used for MMS traffic, the teardown will not happen even
- * when {@code enable} is {@code false}.
- * </p>
*
* @param enable indicates whether to enable ({@code true}) or disable (
* {@code false}) data
@@ -849,15 +863,41 @@
}
protected void onSetInternalDataEnabled(boolean enable) {
+ boolean prevEnabled = getAnyDataEnabled();
if (mInternalDataEnabled != enable) {
synchronized (this) {
mInternalDataEnabled = enable;
}
- if (enable) {
- mRetryMgr.resetRetryCount();
- onTrySetupData(Phone.REASON_DATA_ENABLED);
- } else {
- onCleanUpConnection(true, Phone.REASON_DATA_DISABLED);
+ if (prevEnabled != getAnyDataEnabled()) {
+ if (!prevEnabled) {
+ mRetryMgr.resetRetryCount();
+ onTrySetupData(Phone.REASON_DATA_ENABLED);
+ } else {
+ onCleanUpConnection(true, Phone.REASON_DATA_DISABLED);
+ }
+ }
+ }
+ }
+
+ public synchronized boolean getDataEnabled() {
+ return mDataEnabled;
+ }
+
+ protected void onSetDataEnabled(boolean enable) {
+ boolean prevEnabled = getAnyDataEnabled();
+ if (mDataEnabled != enable) {
+ synchronized (this) {
+ mDataEnabled = enable;
+ }
+ Settings.Secure.putInt(mPhone.getContext().getContentResolver(),
+ Settings.Secure.MOBILE_DATA, enable ? 1 : 0);
+ if (prevEnabled != getAnyDataEnabled()) {
+ if (!prevEnabled) {
+ mRetryMgr.resetRetryCount();
+ onTrySetupData(Phone.REASON_DATA_ENABLED);
+ } else {
+ onCleanUpConnection(true, Phone.REASON_DATA_DISABLED);
+ }
}
}
}
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java
index b005cd3..60df7df 100644
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java
@@ -113,6 +113,7 @@
mDataConnectionTracker = this;
createAllDataConnectionList();
+ broadcastMessenger();
}
@Override
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
index 4713c24..cd0d9e3 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
@@ -160,6 +160,7 @@
/** Create the default connection */
createDataConnection(Phone.APN_TYPE_DEFAULT);
+ broadcastMessenger();
}
@Override
diff --git a/wifi/java/android/net/wifi/WifiStateTracker.java b/wifi/java/android/net/wifi/WifiStateTracker.java
index d0231be..2aff7ec 100644
--- a/wifi/java/android/net/wifi/WifiStateTracker.java
+++ b/wifi/java/android/net/wifi/WifiStateTracker.java
@@ -170,6 +170,13 @@
}
/**
+ * @param enabled
+ */
+ public void setDataEnable(boolean enabled) {
+ android.util.Log.d(TAG, "setDataEnabled: IGNORING enabled=" + enabled);
+ }
+
+ /**
* Check if private DNS route is set for the network
*/
public boolean isPrivateDnsRouteSet() {