Merge "Fix notification disappearance animations."
diff --git a/api/current.txt b/api/current.txt
index cc6cce9..b0621c2 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2452,6 +2452,8 @@
method public abstract void setLogo(android.graphics.drawable.Drawable);
method public abstract void setNavigationMode(int);
method public abstract void setSelectedNavigationItem(int);
+ method public void setSplitBackgroundDrawable(android.graphics.drawable.Drawable);
+ method public void setStackedBackgroundDrawable(android.graphics.drawable.Drawable);
method public abstract void setSubtitle(java.lang.CharSequence);
method public abstract void setSubtitle(int);
method public abstract void setTitle(java.lang.CharSequence);
@@ -6214,6 +6216,7 @@
field public static final java.lang.String FEATURE_USB_ACCESSORY = "android.hardware.usb.accessory";
field public static final java.lang.String FEATURE_USB_HOST = "android.hardware.usb.host";
field public static final java.lang.String FEATURE_WIFI = "android.hardware.wifi";
+ field public static final java.lang.String FEATURE_WIFI_DIRECT = "android.hardware.wifi.direct";
field public static final int GET_ACTIVITIES = 1; // 0x1
field public static final int GET_CONFIGURATIONS = 16384; // 0x4000
field public static final int GET_DISABLED_COMPONENTS = 512; // 0x200
@@ -8896,7 +8899,6 @@
method public int getMinimumWidth();
method public abstract int getOpacity();
method public boolean getPadding(android.graphics.Rect);
- method public int getResolvedLayoutDirectionSelf();
method public int[] getState();
method public android.graphics.Region getTransparentRegion();
method public void inflate(android.content.res.Resources, org.xmlpull.v1.XmlPullParser, android.util.AttributeSet) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
@@ -8931,10 +8933,6 @@
method public abstract void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable);
}
- public static abstract interface Drawable.Callback2 implements android.graphics.drawable.Drawable.Callback {
- method public abstract int getResolvedLayoutDirection(android.graphics.drawable.Drawable);
- }
-
public static abstract class Drawable.ConstantState {
ctor public Drawable.ConstantState();
method public abstract int getChangingConfigurations();
@@ -9326,6 +9324,7 @@
method public boolean isAutoExposureLockSupported();
method public boolean isAutoWhiteBalanceLockSupported();
method public boolean isSmoothZoomSupported();
+ method public boolean isVideoSnapshotSupported();
method public boolean isZoomSupported();
method public void remove(java.lang.String);
method public void removeGpsData();
@@ -22665,7 +22664,7 @@
method public void recycle();
}
- public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback2 android.view.KeyEvent.Callback {
+ public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback {
ctor public View(android.content.Context);
ctor public View(android.content.Context, android.util.AttributeSet);
ctor public View(android.content.Context, android.util.AttributeSet, int);
@@ -22682,7 +22681,6 @@
method public void buildDrawingCache();
method public void buildDrawingCache(boolean);
method public void buildLayer();
- method protected boolean canResolveLayoutDirection();
method public boolean canScrollHorizontally(int);
method public boolean canScrollVertically(int);
method public void cancelLongPress();
@@ -22794,7 +22792,6 @@
method public final android.view.ViewParent getParent();
method public float getPivotX();
method public float getPivotY();
- method public int getResolvedLayoutDirection(android.graphics.drawable.Drawable);
method public android.content.res.Resources getResources();
method public final int getRight();
method protected float getRightFadingEdgeStrength();
@@ -22859,7 +22856,6 @@
method public boolean isHovered();
method public boolean isInEditMode();
method public boolean isInTouchMode();
- method protected static boolean isLayoutDirectionRtl(java.util.Locale);
method public boolean isLayoutRequested();
method public boolean isLongClickable();
method public boolean isOpaque();
@@ -22945,10 +22941,8 @@
method public void requestLayout();
method public boolean requestRectangleOnScreen(android.graphics.Rect);
method public boolean requestRectangleOnScreen(android.graphics.Rect, boolean);
- method protected void resetResolvedTextDirection();
method public static int resolveSize(int, int);
method public static int resolveSizeAndState(int, int, int);
- method protected void resolveTextDirection();
method public void restoreHierarchyState(android.util.SparseArray<android.os.Parcelable>);
method public void saveHierarchyState(android.util.SparseArray<android.os.Parcelable>);
method public void scheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable, long);
@@ -23049,7 +23043,6 @@
method public boolean willNotCacheDrawing();
method public boolean willNotDraw();
field public static android.util.Property ALPHA;
- field protected static int DEFAULT_TEXT_DIRECTION;
field public static final int DRAWING_CACHE_QUALITY_AUTO = 0; // 0x0
field public static final int DRAWING_CACHE_QUALITY_HIGH = 1048576; // 0x100000
field public static final int DRAWING_CACHE_QUALITY_LOW = 524288; // 0x80000
@@ -23378,6 +23371,7 @@
method public boolean requestSendAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent);
method public void requestTransparentRegion(android.view.View);
method protected void resetResolvedLayoutDirection();
+ method protected void resetResolvedTextDirection();
method public void scheduleLayoutAnimation();
method public void setAddStatesFromChildren(boolean);
method public void setAlwaysDrawnWithCacheEnabled(boolean);
@@ -27175,6 +27169,7 @@
method protected void resetResolvedDrawables();
method protected void resetResolvedLayoutDirection();
method protected void resolveDrawables();
+ method protected void resolveTextDirection();
method public void setAllCaps(boolean);
method public final void setAutoLinkMask(int);
method public void setCompoundDrawablePadding(int);
diff --git a/core/java/android/animation/LayoutTransition.java b/core/java/android/animation/LayoutTransition.java
index 9e2b833..355b1fc 100644
--- a/core/java/android/animation/LayoutTransition.java
+++ b/core/java/android/animation/LayoutTransition.java
@@ -793,7 +793,9 @@
* @hide
*/
public void startChangingAnimations() {
- for (Animator anim : currentChangingAnimations.values()) {
+ LinkedHashMap<View, Animator> currentAnimCopy =
+ (LinkedHashMap<View, Animator>) currentChangingAnimations.clone();
+ for (Animator anim : currentAnimCopy.values()) {
if (anim instanceof ObjectAnimator) {
((ObjectAnimator) anim).setCurrentPlayTime(0);
}
@@ -802,6 +804,23 @@
}
/**
+ * Ends the animations that are set up for a CHANGING transition. This is a variant of
+ * startChangingAnimations() which is called when the window the transition is playing in
+ * is not visible. We need to make sure the animations put their targets in their end states
+ * and that the transition finishes to remove any mid-process state (such as isRunning()).
+ *
+ * @hide
+ */
+ public void endChangingAnimations() {
+ LinkedHashMap<View, Animator> currentAnimCopy =
+ (LinkedHashMap<View, Animator>) currentChangingAnimations.clone();
+ for (Animator anim : currentAnimCopy.values()) {
+ anim.start();
+ anim.end();
+ }
+ }
+
+ /**
* 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/ActionBar.java b/core/java/android/app/ActionBar.java
index 46dc5ff..0d4a067 100644
--- a/core/java/android/app/ActionBar.java
+++ b/core/java/android/app/ActionBar.java
@@ -402,13 +402,33 @@
public abstract void setDisplayShowCustomEnabled(boolean showCustom);
/**
- * Set the ActionBar's background.
+ * Set the ActionBar's background. This will be used for the primary
+ * action bar.
*
* @param d Background drawable
+ * @see #setStackedBackgroundDrawable(Drawable)
+ * @see #setSplitBackgroundDrawable(Drawable)
*/
public abstract void setBackgroundDrawable(Drawable d);
/**
+ * Set the ActionBar's stacked background. This will appear
+ * in the second row/stacked bar on some devices and configurations.
+ *
+ * @param d Background drawable for the stacked row
+ */
+ public void setStackedBackgroundDrawable(Drawable d) { }
+
+ /**
+ * Set the ActionBar's split background. This will appear in
+ * the split action bar containing menu-provided action buttons
+ * on some devices and configurations.
+ *
+ * @param d Background drawable for the split bar
+ */
+ public void setSplitBackgroundDrawable(Drawable d) { }
+
+ /**
* @return The current custom view.
*/
public abstract View getCustomView();
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 5c641f1..b4e3988 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1021,6 +1021,13 @@
public static final String FEATURE_WIFI = "android.hardware.wifi";
/**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports Wi-Fi Direct networking.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_WIFI_DIRECT = "android.hardware.wifi.direct";
+
+ /**
* Action to external storage service to clean out removed apps.
* @hide
*/
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 63f2244..58f7869 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -3233,7 +3233,6 @@
* captured pictures.
*
* @return true if video snapshot is supported.
- * @hide
*/
public boolean isVideoSnapshotSupported() {
String str = get(KEY_VIDEO_SNAPSHOT_SUPPORTED);
diff --git a/core/java/android/hardware/SensorEvent.java b/core/java/android/hardware/SensorEvent.java
index 0411b5c..784bcc5 100644
--- a/core/java/android/hardware/SensorEvent.java
+++ b/core/java/android/hardware/SensorEvent.java
@@ -154,16 +154,16 @@
* All values are in micro-Tesla (uT) and measure the ambient magnetic field
* in the X, Y and Z axis.
*
- * <h4>{@link android.hardware.Sensor#TYPE_GYROSCOPE Sensor.TYPE_GYROSCOPE}:</h4>
- * All values are in radians/second and measure the rate of rotation
- * around the X, Y and Z axis. The coordinate system is the same as is
- * used for the acceleration sensor. Rotation is positive in the counter-clockwise
- * direction. That is, an observer looking from some positive location on the x, y.
- * or z axis at a device positioned on the origin would report positive rotation
- * if the device appeared to be rotating counter clockwise. Note that this is the
- * standard mathematical definition of positive rotation and does not agree with the
- * definition of roll given earlier.
- *
+ * <h4>{@link android.hardware.Sensor#TYPE_GYROSCOPE Sensor.TYPE_GYROSCOPE}:
+ * </h4> All values are in radians/second and measure the rate of rotation
+ * around the device's local X, Y and Z axis. The coordinate system is the
+ * same as is used for the acceleration sensor. Rotation is positive in the
+ * counter-clockwise direction. That is, an observer looking from some
+ * positive location on the x, y or z axis at a device positioned on the
+ * origin would report positive rotation if the device appeared to be
+ * rotating counter clockwise. Note that this is the standard mathematical
+ * definition of positive rotation and does not agree with the definition of
+ * roll given earlier.
* <ul>
* <p>
* values[0]: Angular speed around the x-axis
@@ -176,28 +176,61 @@
* </p>
* </ul>
* <p>
- * Typically the output of the gyroscope is integrated over time to calculate
- * an angle, for example:
+ * Typically the output of the gyroscope is integrated over time to
+ * calculate a rotation describing the change of angles over the timestep,
+ * for example:
* </p>
+ *
* <pre class="prettyprint">
* private static final float NS2S = 1.0f / 1000000000.0f;
+ * private final float[] deltaRotationVector = new float[4]();
* private float timestamp;
- * public void onSensorChanged(SensorEvent event)
- * {
+ *
+ * public void onSensorChanged(SensorEvent event) {
+ * // This timestep's delta rotation to be multiplied by the current rotation
+ * // after computing it from the gyro sample data.
* if (timestamp != 0) {
* final float dT = (event.timestamp - timestamp) * NS2S;
- * angle[0] += event.values[0] * dT;
- * angle[1] += event.values[1] * dT;
- * angle[2] += event.values[2] * dT;
+ * // Axis of the rotation sample, not normalized yet.
+ * float axisX = event.values[0];
+ * float axisY = event.values[1];
+ * float axisZ = event.values[2];
+ *
+ * // Calculate the angular speed of the sample
+ * float omegaMagnitude = sqrt(axisX*axisX + axisY*axisY + axisZ*axisZ);
+ *
+ * // Normalize the rotation vector if it's big enough to get the axis
+ * if (omegaMagnitude > EPSILON) {
+ * axisX /= omegaMagnitude;
+ * axisY /= omegaMagnitude;
+ * axisZ /= omegaMagnitude;
+ * }
+ *
+ * // Integrate around this axis with the angular speed by the timestep
+ * // in order to get a delta rotation from this sample over the timestep
+ * // We will convert this axis-angle representation of the delta rotation
+ * // into a quaternion before turning it into the rotation matrix.
+ * float thetaOverTwo = omegaMagnitude * dT / 2.0f;
+ * float sinThetaOverTwo = sin(thetaOverTwo);
+ * float cosThetaOverTwo = cos(thetaOverTwo);
+ * deltaRotationVector[0] = sinThetaOverTwo * axisX;
+ * deltaRotationVector[1] = sinThetaOverTwo * axisY;
+ * deltaRotationVector[2] = sinThetaOverTwo * axisZ;
+ * deltaRotationVector[3] = cosThetaOverTwo;
* }
* timestamp = event.timestamp;
+ * float[] deltaRotationMatrix = new float[9];
+ * SensorManager.getRotationMatrixFromVector(deltaRotationMatrix, deltaRotationVector);
+ * // User code should concatenate the delta rotation we computed with the current rotation
+ * // in order to get the updated rotation.
+ * // rotationCurrent = rotationCurrent * deltaRotationMatrix;
* }
* </pre>
- *
- * <p>In practice, the gyroscope noise and offset will introduce some errors which need
- * to be compensated for. This is usually done using the information from other
- * sensors, but is beyond the scope of this document.</p>
- *
+ * <p>
+ * In practice, the gyroscope noise and offset will introduce some errors
+ * which need to be compensated for. This is usually done using the
+ * information from other sensors, but is beyond the scope of this document.
+ * </p>
* <h4>{@link android.hardware.Sensor#TYPE_LIGHT Sensor.TYPE_LIGHT}:</h4>
* <ul>
* <p>
diff --git a/core/java/android/inputmethodservice/ExtractEditText.java b/core/java/android/inputmethodservice/ExtractEditText.java
index 44e7e52..4fc63ed 100644
--- a/core/java/android/inputmethodservice/ExtractEditText.java
+++ b/core/java/android/inputmethodservice/ExtractEditText.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.util.AttributeSet;
import android.view.inputmethod.ExtractedText;
+import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
/***
@@ -142,4 +143,17 @@
@Override public boolean hasFocus() {
return this.isEnabled();
}
+
+ /**
+ * @hide
+ */
+ @Override protected void viewClicked(InputMethodManager imm) {
+ // As an instance of this class is supposed to be owned by IMS,
+ // and it has a reference to the IMS (the current IME),
+ // we just need to call back its onViewClicked() here.
+ // It should be good to avoid unnecessary IPCs by doing this as well.
+ if (mIME != null) {
+ mIME.onViewClicked(false);
+ }
+ }
}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 370e22a..7d3cd92 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -618,7 +618,7 @@
mTheme = Resources.selectSystemTheme(mTheme,
getApplicationInfo().targetSdkVersion,
android.R.style.Theme_InputMethod,
- android.R.style.Theme_Holo,
+ android.R.style.Theme_Holo_InputMethod,
android.R.style.Theme_DeviceDefault_InputMethod);
super.setTheme(mTheme);
super.onCreate();
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
index 3918cfd..f3be39c 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -102,6 +102,21 @@
this.txPackets = txPackets;
this.operations = operations;
}
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("iface=").append(iface);
+ builder.append(" uid=").append(uid);
+ builder.append(" set=").append(setToString(set));
+ builder.append(" tag=").append(tagToString(tag));
+ builder.append(" rxBytes=").append(rxBytes);
+ builder.append(" rxPackets=").append(rxPackets);
+ builder.append(" txBytes=").append(txBytes);
+ builder.append(" txPackets=").append(txPackets);
+ builder.append(" operations=").append(operations);
+ return builder.toString();
+ }
}
public NetworkStats(long elapsedRealtime, int initialSize) {
diff --git a/core/java/android/text/TextPaint.java b/core/java/android/text/TextPaint.java
index 625d869..afd9892 100644
--- a/core/java/android/text/TextPaint.java
+++ b/core/java/android/text/TextPaint.java
@@ -72,8 +72,15 @@
linkColor = tp.linkColor;
drawableState = tp.drawableState;
density = tp.density;
- underlineColors = tp.underlineColors;
- underlineThicknesses = tp.underlineThicknesses;
+
+ if (tp.underlineColors != null) {
+ if (underlineColors == null || underlineColors.length < tp.underlineCount) {
+ underlineColors = new int[tp.underlineCount];
+ underlineThicknesses = new float[tp.underlineCount];
+ }
+ System.arraycopy(tp.underlineColors, 0, underlineColors, 0, tp.underlineCount);
+ System.arraycopy(tp.underlineThicknesses, 0, underlineThicknesses, 0, tp.underlineCount);
+ }
underlineCount = tp.underlineCount;
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index ba23218..fd60813f 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -636,7 +636,8 @@
*
* @see android.view.ViewGroup
*/
-public class View implements Drawable.Callback2, KeyEvent.Callback, AccessibilityEventSource {
+public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Callback,
+ AccessibilityEventSource {
private static final boolean DBG = false;
/**
@@ -2583,6 +2584,8 @@
/**
* Default text direction is inherited
+ *
+ * @hide
*/
protected static int DEFAULT_TEXT_DIRECTION = TEXT_DIRECTION_INHERIT;
@@ -9294,6 +9297,11 @@
recomputePadding();
}
+ /**
+ * Return true if layout direction resolution can be done
+ *
+ * @hide
+ */
protected boolean canResolveLayoutDirection() {
switch (getLayoutDirection()) {
case LAYOUT_DIRECTION_INHERIT:
@@ -9322,6 +9330,8 @@
*
* @param locale Locale to check
* @return true if a Locale is corresponding to a RTL script.
+ *
+ * @hide
*/
protected static boolean isLayoutDirectionRtl(Locale locale) {
return (LocaleUtil.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE ==
@@ -13240,7 +13250,6 @@
* {@link #TEXT_DIRECTION_INHERIT},
* {@link #TEXT_DIRECTION_FIRST_STRONG}
* {@link #TEXT_DIRECTION_ANY_RTL},
- * {@link #TEXT_DIRECTION_CHAR_COUNT},
* {@link #TEXT_DIRECTION_LTR},
* {@link #TEXT_DIRECTION_RTL},
*
@@ -13258,7 +13267,6 @@
* {@link #TEXT_DIRECTION_INHERIT},
* {@link #TEXT_DIRECTION_FIRST_STRONG}
* {@link #TEXT_DIRECTION_ANY_RTL},
- * {@link #TEXT_DIRECTION_CHAR_COUNT},
* {@link #TEXT_DIRECTION_LTR},
* {@link #TEXT_DIRECTION_RTL},
*
@@ -13279,7 +13287,6 @@
*
* {@link #TEXT_DIRECTION_FIRST_STRONG}
* {@link #TEXT_DIRECTION_ANY_RTL},
- * {@link #TEXT_DIRECTION_CHAR_COUNT},
* {@link #TEXT_DIRECTION_LTR},
* {@link #TEXT_DIRECTION_RTL},
*
@@ -13294,6 +13301,8 @@
/**
* Resolve the text direction.
+ *
+ * @hide
*/
protected void resolveTextDirection() {
if (mTextDirection != TEXT_DIRECTION_INHERIT) {
@@ -13309,6 +13318,8 @@
/**
* Reset resolved text direction. Will be resolved during a call to getResolvedTextDirection().
+ *
+ * @hide
*/
protected void resetResolvedTextDirection() {
mResolvedTextDirection = TEXT_DIRECTION_INHERIT;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 2e2b3d6..fb3f6e8 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1575,13 +1575,13 @@
boolean cancelDraw = attachInfo.mTreeObserver.dispatchOnPreDraw() ||
viewVisibility != View.VISIBLE;
- if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
- for (int i = 0; i < mPendingTransitions.size(); ++i) {
- mPendingTransitions.get(i).startChangingAnimations();
- }
- mPendingTransitions.clear();
- }
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;
@@ -1619,7 +1619,13 @@
}
}
} else {
-
+ // End any pending transitions on this non-visible window
+ if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
+ for (int i = 0; i < mPendingTransitions.size(); ++i) {
+ mPendingTransitions.get(i).endChangingAnimations();
+ }
+ mPendingTransitions.clear();
+ }
// We were supposed to report when we are done drawing. Since we canceled the
// draw, remember it here.
if ((relayoutResult&WindowManagerImpl.RELAYOUT_FIRST_TIME) != 0) {
@@ -4537,7 +4543,7 @@
predicate.init(accessibilityId);
View root = ViewRootImpl.this.mView;
View target = root.findViewByPredicate(predicate);
- if (target != null && target.isShown()) {
+ if (target != null && target.getVisibility() == View.VISIBLE) {
info = target.createAccessibilityNodeInfo();
}
} finally {
@@ -4580,7 +4586,7 @@
try {
View root = ViewRootImpl.this.mView;
View target = root.findViewById(viewId);
- if (target != null && target.isShown()) {
+ if (target != null && target.getVisibility() == View.VISIBLE) {
info = target.createAccessibilityNodeInfo();
}
} finally {
@@ -4631,14 +4637,14 @@
ArrayList<View> foundViews = mAttachInfo.mFocusablesTempList;
foundViews.clear();
- View root;
+ View root = null;
if (accessibilityViewId != View.NO_ID) {
root = findViewByAccessibilityId(accessibilityViewId);
} else {
root = ViewRootImpl.this.mView;
}
- if (root == null || !root.isShown()) {
+ if (root == null || root.getVisibility() != View.VISIBLE) {
return;
}
@@ -4653,7 +4659,7 @@
final int viewCount = foundViews.size();
for (int i = 0; i < viewCount; i++) {
View foundView = foundViews.get(i);
- if (foundView.isShown()) {
+ if (foundView.getVisibility() == View.VISIBLE) {
infos.add(foundView.createAccessibilityNodeInfo());
}
}
@@ -4726,7 +4732,7 @@
private boolean performActionFocus(int accessibilityId) {
View target = findViewByAccessibilityId(accessibilityId);
- if (target == null) {
+ if (target == null || target.getVisibility() != View.VISIBLE) {
return false;
}
// Get out of touch mode since accessibility wants to move focus around.
@@ -4736,7 +4742,7 @@
private boolean performActionClearFocus(int accessibilityId) {
View target = findViewByAccessibilityId(accessibilityId);
- if (target == null) {
+ if (target == null || target.getVisibility() != View.VISIBLE) {
return false;
}
if (!target.isFocused()) {
@@ -4748,7 +4754,7 @@
private boolean performActionSelect(int accessibilityId) {
View target = findViewByAccessibilityId(accessibilityId);
- if (target == null) {
+ if (target == null || target.getVisibility() != View.VISIBLE) {
return false;
}
if (target.isSelected()) {
@@ -4760,7 +4766,7 @@
private boolean performActionClearSelection(int accessibilityId) {
View target = findViewByAccessibilityId(accessibilityId);
- if (target == null) {
+ if (target == null || target.getVisibility() != View.VISIBLE) {
return false;
}
if (!target.isSelected()) {
@@ -4777,18 +4783,21 @@
}
mFindByAccessibilityIdPredicate.init(accessibilityId);
View foundView = root.findViewByPredicate(mFindByAccessibilityIdPredicate);
- return (foundView != null && foundView.isShown()) ? foundView : null;
+ if (foundView == null || foundView.getVisibility() != View.VISIBLE) {
+ return null;
+ }
+ return foundView;
}
private final class FindByAccessibilitytIdPredicate implements Predicate<View> {
- public int mSerchedId;
+ public int mSearchedId;
public void init(int searchedId) {
- mSerchedId = searchedId;
+ mSearchedId = searchedId;
}
public boolean apply(View view) {
- return (view.getAccessibilityViewId() == mSerchedId);
+ return (view.getAccessibilityViewId() == mSearchedId);
}
}
}
diff --git a/core/java/android/view/animation/AnimationSet.java b/core/java/android/view/animation/AnimationSet.java
index 4f2542b..58373bc 100644
--- a/core/java/android/view/animation/AnimationSet.java
+++ b/core/java/android/view/animation/AnimationSet.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.content.res.TypedArray;
+import android.os.Build;
import android.util.AttributeSet;
import android.graphics.RectF;
@@ -31,6 +32,22 @@
* If AnimationSet sets any properties that its children also set
* (for example, duration or fillBefore), the values of AnimationSet
* override the child values.
+ *
+ * <p>The way that AnimationSet inherits behavior from Animation is important to
+ * understand. Some of the Animation attributes applied to AnimationSet affect the
+ * AnimationSet itself, some are pushed down to the children, and some are ignored,
+ * as follows:
+ * <ul>
+ * <li>duration, repeatMode, fillBefore, fillAfter: These properties, when set
+ * on an AnimationSet object, will be pushed down to all child animations.</li>
+ * <li>repeatCount, fillEnabled: These properties are ignored for AnimationSet.</li>
+ * <li>startOffset, shareInterpolator: These properties apply to the AnimationSet itself.</li>
+ * </ul>
+ * Starting with {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH},
+ * the behavior of these properties is the same in XML resources and at runtime (prior to that
+ * release, the values set in XML were ignored for AnimationSet). That is, calling
+ * <code>setDuration(500)</code> on an AnimationSet has the same effect as declaring
+ * <code>android:duration="500"</code> in an XML resource for an AnimationSet object.</p>
*/
public class AnimationSet extends Animation {
private static final int PROPERTY_FILL_AFTER_MASK = 0x1;
@@ -69,7 +86,26 @@
setFlag(PROPERTY_SHARE_INTERPOLATOR_MASK,
a.getBoolean(com.android.internal.R.styleable.AnimationSet_shareInterpolator, true));
init();
-
+
+ if (context.getApplicationInfo().targetSdkVersion >=
+ Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ if (a.hasValue(com.android.internal.R.styleable.Animation_duration)) {
+ mFlags |= PROPERTY_DURATION_MASK;
+ }
+ if (a.hasValue(com.android.internal.R.styleable.Animation_fillBefore)) {
+ mFlags |= PROPERTY_FILL_BEFORE_MASK;
+ }
+ if (a.hasValue(com.android.internal.R.styleable.Animation_fillAfter)) {
+ mFlags |= PROPERTY_FILL_AFTER_MASK;
+ }
+ if (a.hasValue(com.android.internal.R.styleable.Animation_repeatMode)) {
+ mFlags |= PROPERTY_REPEAT_MODE_MASK;
+ }
+ if (a.hasValue(com.android.internal.R.styleable.Animation_startOffset)) {
+ mFlags |= PROPERTY_START_OFFSET_MASK;
+ }
+ }
+
a.recycle();
}
@@ -112,7 +148,6 @@
private void init() {
mStartTime = 0;
- mDuration = 0;
}
@Override
@@ -171,6 +206,7 @@
public void setDuration(long durationMillis) {
mFlags |= PROPERTY_DURATION_MASK;
super.setDuration(durationMillis);
+ mLastEnd = mStartOffset + mDuration;
}
/**
@@ -192,12 +228,16 @@
mFlags |= PROPERTY_CHANGE_BOUNDS_MASK;
}
- if (mAnimations.size() == 1) {
- mDuration = a.getStartOffset() + a.getDuration();
+ if ((mFlags & PROPERTY_DURATION_MASK) == PROPERTY_DURATION_MASK) {
mLastEnd = mStartOffset + mDuration;
} else {
- mLastEnd = Math.max(mLastEnd, a.getStartOffset() + a.getDuration());
- mDuration = mLastEnd - mStartOffset;
+ if (mAnimations.size() == 1) {
+ mDuration = a.getStartOffset() + a.getDuration();
+ mLastEnd = mStartOffset + mDuration;
+ } else {
+ mLastEnd = Math.max(mLastEnd, a.getStartOffset() + a.getDuration());
+ mDuration = mLastEnd - mStartOffset;
+ }
}
mDirty = true;
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 5bb0ef2..7ba93da0 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -20,6 +20,7 @@
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.ClipboardManager;
+import android.content.ComponentCallbacks2;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
@@ -382,6 +383,39 @@
}
}
+ private static class OnTrimMemoryListener implements ComponentCallbacks2 {
+ private static OnTrimMemoryListener sInstance = null;
+
+ static void init(Context c) {
+ if (sInstance == null) {
+ sInstance = new OnTrimMemoryListener(c.getApplicationContext());
+ }
+ }
+
+ private OnTrimMemoryListener(Context c) {
+ c.registerComponentCallbacks(this);
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ // Ignore
+ }
+
+ @Override
+ public void onLowMemory() {
+ // Ignore
+ }
+
+ @Override
+ public void onTrimMemory(int level) {
+ if (DebugFlags.WEB_VIEW) {
+ Log.d("WebView", "onTrimMemory: " + level);
+ }
+ WebView.nativeOnTrimMemory(level);
+ }
+
+ }
+
// A final CallbackProxy shared by WebViewCore and BrowserFrame.
private final CallbackProxy mCallbackProxy;
@@ -1194,6 +1228,8 @@
}
private void init() {
+ OnTrimMemoryListener.init(getContext());
+
setWillNotDraw(false);
setFocusable(true);
setFocusableInTouchMode(true);
@@ -9404,4 +9440,8 @@
native boolean nativeSetProperty(String key, String value);
native String nativeGetProperty(String key);
private native void nativeGetTextSelectionRegion(Region region);
+ /**
+ * See {@link ComponentCallbacks2} for the trim levels and descriptions
+ */
+ private static native void nativeOnTrimMemory(int level);
}
diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java
index b945038..2d10bbe 100644
--- a/core/java/android/widget/AdapterView.java
+++ b/core/java/android/widget/AdapterView.java
@@ -886,9 +886,11 @@
event.setEventType(AccessibilityEvent.TYPE_VIEW_SELECTED);
}
- // We first get a chance to populate the event.
- onPopulateAccessibilityEvent(event);
-
+ View selectedView = getSelectedView();
+ if (selectedView != null && selectedView.getVisibility() == VISIBLE) {
+ // We first get a chance to populate the event.
+ onPopulateAccessibilityEvent(event);
+ }
return false;
}
@@ -896,10 +898,7 @@
public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
// We send selection events only from AdapterView to avoid
// generation of such event for each child.
- View selectedView = getSelectedView();
- if (selectedView != null) {
- selectedView.dispatchPopulateAccessibilityEvent(event);
- }
+ getSelectedView().dispatchPopulateAccessibilityEvent(event);
}
@Override
diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java
index a5cf62e..6edfd59 100644
--- a/core/java/android/widget/RelativeLayout.java
+++ b/core/java/android/widget/RelativeLayout.java
@@ -961,7 +961,8 @@
}
for (View view : mTopToBottomLeftToRightSet) {
- if (view.dispatchPopulateAccessibilityEvent(event)) {
+ if (view.getVisibility() == View.VISIBLE
+ && view.dispatchPopulateAccessibilityEvent(event)) {
mTopToBottomLeftToRightSet.clear();
return true;
}
diff --git a/core/java/android/widget/TabWidget.java b/core/java/android/widget/TabWidget.java
index 9afb625..191c4ca 100644
--- a/core/java/android/widget/TabWidget.java
+++ b/core/java/android/widget/TabWidget.java
@@ -405,7 +405,10 @@
onPopulateAccessibilityEvent(event);
// Dispatch only to the selected tab.
if (mSelectedTab != -1) {
- return getChildTabViewAt(mSelectedTab).dispatchPopulateAccessibilityEvent(event);
+ View tabView = getChildTabViewAt(mSelectedTab);
+ if (tabView != null && tabView.getVisibility() == VISIBLE) {
+ return tabView.dispatchPopulateAccessibilityEvent(event);
+ }
}
return false;
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 7f410aa..d88999b 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -5432,8 +5432,8 @@
if (mMovement != null && mText instanceof Editable
&& mLayout != null && onCheckIsTextEditor()) {
InputMethodManager imm = InputMethodManager.peekInstance();
+ viewClicked(imm);
if (imm != null) {
- imm.viewClicked(this);
imm.showSoftInput(this, 0);
}
}
@@ -8346,9 +8346,7 @@
if (touchIsFinished && (isTextEditable() || mTextIsSelectable)) {
// Show the IME, except when selecting in read-only text.
final InputMethodManager imm = InputMethodManager.peekInstance();
- if (imm != null) {
- imm.viewClicked(this);
- }
+ viewClicked(imm);
if (!mTextIsSelectable) {
handled |= imm != null && imm.showSoftInput(this, 0);
}
@@ -9555,6 +9553,8 @@
public void dismiss() {
super.dismiss();
+ TextView.this.getPositionListener().removeSubscriber(SuggestionsPopupWindow.this);
+
if ((mText instanceof Editable) && mSuggestionRangeSpan != null) {
((Editable) mText).removeSpan(mSuggestionRangeSpan);
}
@@ -11308,6 +11308,15 @@
mResolvedDrawables = false;
}
+ /**
+ * @hide
+ */
+ protected void viewClicked(InputMethodManager imm) {
+ if (imm != null) {
+ imm.viewClicked(this);
+ }
+ }
+
@ViewDebug.ExportedProperty(category = "text")
private CharSequence mText;
private CharSequence mTransformed;
diff --git a/core/java/com/android/internal/app/ActionBarImpl.java b/core/java/com/android/internal/app/ActionBarImpl.java
index 90d19fd..ccca22e 100644
--- a/core/java/com/android/internal/app/ActionBarImpl.java
+++ b/core/java/com/android/internal/app/ActionBarImpl.java
@@ -346,7 +346,17 @@
}
public void setBackgroundDrawable(Drawable d) {
- mContainerView.setBackgroundDrawable(d);
+ mContainerView.setPrimaryBackground(d);
+ }
+
+ public void setStackedBackgroundDrawable(Drawable d) {
+ mContainerView.setStackedBackground(d);
+ }
+
+ public void setSplitBackgroundDrawable(Drawable d) {
+ if (mSplitView != null) {
+ mSplitView.setSplitBackground(d);
+ }
}
public View getCustomView() {
diff --git a/core/java/com/android/internal/backup/BackupConstants.java b/core/java/com/android/internal/backup/BackupConstants.java
index 3ee11bd..906b5d5 100644
--- a/core/java/com/android/internal/backup/BackupConstants.java
+++ b/core/java/com/android/internal/backup/BackupConstants.java
@@ -23,4 +23,5 @@
public static final int TRANSPORT_OK = 0;
public static final int TRANSPORT_ERROR = 1;
public static final int TRANSPORT_NOT_INITIALIZED = 2;
+ public static final int AGENT_ERROR = 3;
}
diff --git a/core/java/com/android/internal/widget/ActionBarContainer.java b/core/java/com/android/internal/widget/ActionBarContainer.java
index fd9ee08..f95de62 100644
--- a/core/java/com/android/internal/widget/ActionBarContainer.java
+++ b/core/java/com/android/internal/widget/ActionBarContainer.java
@@ -76,6 +76,21 @@
mActionBarView = (ActionBarView) findViewById(com.android.internal.R.id.action_bar);
}
+ public void setPrimaryBackground(Drawable bg) {
+ mBackground = bg;
+ invalidate();
+ }
+
+ public void setStackedBackground(Drawable bg) {
+ mStackedBackground = bg;
+ invalidate();
+ }
+
+ public void setSplitBackground(Drawable bg) {
+ mSplitBackground = bg;
+ invalidate();
+ }
+
/**
* Set the action bar into a "transitioning" state. While transitioning
* the bar will block focus and touch from all of its descendants. This
diff --git a/core/jni/android/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp
index b01dcd8..9313d0a 100644
--- a/core/jni/android/graphics/Canvas.cpp
+++ b/core/jni/android/graphics/Canvas.cpp
@@ -760,7 +760,8 @@
jint count = end - start;
sp<TextLayoutCacheValue> value;
#if USE_TEXT_LAYOUT_CACHE
- value = gTextLayoutCache.getValue(paint, textArray, start, count, end, flags);
+ value = TextLayoutCache::getInstance().getValue(paint, textArray, start, count,
+ end, flags);
if (value == NULL) {
LOGE("Cannot get TextLayoutCache value");
return ;
@@ -780,7 +781,8 @@
sp<TextLayoutCacheValue> value;
#if USE_TEXT_LAYOUT_CACHE
- value = gTextLayoutCache.getValue(paint, textArray, start, count, contextCount, flags);
+ value = TextLayoutCache::getInstance().getValue(paint, textArray, start, count,
+ contextCount, flags);
if (value == NULL) {
LOGE("Cannot get TextLayoutCache value");
return ;
diff --git a/core/jni/android/graphics/TextLayout.cpp b/core/jni/android/graphics/TextLayout.cpp
index 7e89a37..fa9a7b7 100644
--- a/core/jni/android/graphics/TextLayout.cpp
+++ b/core/jni/android/graphics/TextLayout.cpp
@@ -257,7 +257,7 @@
sp<TextLayoutCacheValue> value;
#if USE_TEXT_LAYOUT_CACHE
// Return advances from the cache. Compute them if needed
- value = gTextLayoutCache.getValue(
+ value = TextLayoutCache::getInstance().getValue(
paint, chars, start, count, contextCount, dirFlags);
#else
value = new TextLayoutCacheValue();
diff --git a/core/jni/android/graphics/TextLayout.h b/core/jni/android/graphics/TextLayout.h
index 9bb1b92..0a29d78 100644
--- a/core/jni/android/graphics/TextLayout.h
+++ b/core/jni/android/graphics/TextLayout.h
@@ -41,11 +41,6 @@
*/
#define USE_TEXT_LAYOUT_CACHE 1
-
-#if USE_TEXT_LAYOUT_CACHE
- static TextLayoutCache gTextLayoutCache;
-#endif
-
enum {
kBidi_LTR = 0,
kBidi_RTL = 1,
diff --git a/core/jni/android/graphics/TextLayoutCache.cpp b/core/jni/android/graphics/TextLayoutCache.cpp
index f04c5eb5..7f79277 100644
--- a/core/jni/android/graphics/TextLayoutCache.cpp
+++ b/core/jni/android/graphics/TextLayoutCache.cpp
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+#define LOG_TAG "TextLayoutCache"
+
#include "TextLayoutCache.h"
#include "TextLayout.h"
@@ -23,6 +25,12 @@
namespace android {
+//--------------------------------------------------------------------------------------------------
+#if USE_TEXT_LAYOUT_CACHE
+ ANDROID_SINGLETON_STATIC_INSTANCE(TextLayoutCache);
+#endif
+//--------------------------------------------------------------------------------------------------
+
TextLayoutCache::TextLayoutCache() :
mCache(GenerationCache<TextLayoutCacheKey, sp<TextLayoutCacheValue> >::kUnlimitedCapacity),
mSize(0), mMaxSize(MB(DEFAULT_TEXT_LAYOUT_CACHE_SIZE_IN_MB)),
@@ -30,13 +38,6 @@
init();
}
-TextLayoutCache::TextLayoutCache(uint32_t max):
- mCache(GenerationCache<TextLayoutCacheKey, sp<TextLayoutCacheValue> >::kUnlimitedCapacity),
- mSize(0), mMaxSize(max),
- mCacheHitCount(0), mNanosecondsSaved(0) {
- init();
-}
-
TextLayoutCache::~TextLayoutCache() {
mCache.clear();
}
@@ -46,25 +47,21 @@
mDebugLevel = readRtlDebugLevel();
mDebugEnabled = mDebugLevel & kRtlDebugCaches;
- LOGD("Using TextLayoutCache debug level: %d - Debug Enabled: %d", mDebugLevel, mDebugEnabled);
+ LOGD("Using debug level: %d - Debug Enabled: %d", mDebugLevel, mDebugEnabled);
mCacheStartTime = systemTime(SYSTEM_TIME_MONOTONIC);
- if (mDebugEnabled) {
- LOGD("TextLayoutCache start time: %lld", mCacheStartTime);
- }
- mInitialized = true;
if (mDebugEnabled) {
+ LOGD("Start time: %lld", mCacheStartTime);
#if RTL_USE_HARFBUZZ
- LOGD("TextLayoutCache is using HARFBUZZ");
+ LOGD("Using HARFBUZZ");
#else
- LOGD("TextLayoutCache is using ICU");
+ LOGD("Using ICU");
#endif
+ LOGD("Initialization is done");
}
- if (mDebugEnabled) {
- LOGD("TextLayoutCache initialization is done");
- }
+ mInitialized = true;
}
/*
@@ -147,8 +144,7 @@
// Cleanup to make some room if needed
if (mSize + size > mMaxSize) {
if (mDebugEnabled) {
- LOGD("TextLayoutCache: need to clean some entries "
- "for making some room for a new entry");
+ LOGD("Need to clean some entries for making some room for a new entry");
}
while (mSize + size > mMaxSize) {
// This will call the callback
@@ -213,7 +209,7 @@
float remainingPercent = 100 * ((mMaxSize - mSize) / ((float)mMaxSize));
float timeRunningInSec = (systemTime(SYSTEM_TIME_MONOTONIC) - mCacheStartTime) / 1000000000;
LOGD("------------------------------------------------");
- LOGD("TextLayoutCache stats");
+ LOGD("Cache stats");
LOGD("------------------------------------------------");
LOGD("pid : %d", getpid());
LOGD("running : %.0f seconds", timeRunningInSec);
diff --git a/core/jni/android/graphics/TextLayoutCache.h b/core/jni/android/graphics/TextLayoutCache.h
index 10dee87..0d8d71f 100644
--- a/core/jni/android/graphics/TextLayoutCache.h
+++ b/core/jni/android/graphics/TextLayoutCache.h
@@ -25,6 +25,7 @@
#include <utils/GenerationCache.h>
#include <utils/Compare.h>
#include <utils/RefBase.h>
+#include <utils/Singleton.h>
#include <SkPaint.h>
#include <SkTemplates.h>
@@ -187,11 +188,11 @@
/**
* Cache of text layout information.
*/
-class TextLayoutCache : public OnEntryRemoved<TextLayoutCacheKey, sp<TextLayoutCacheValue> >
+class TextLayoutCache : public OnEntryRemoved<TextLayoutCacheKey, sp<TextLayoutCacheValue> >,
+ public Singleton<TextLayoutCache>
{
public:
TextLayoutCache();
- TextLayoutCache(uint32_t maxByteSize);
virtual ~TextLayoutCache();
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index bcf8e71..395e417 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -477,7 +477,7 @@
#if RTL_USE_HARFBUZZ
sp<TextLayoutCacheValue> value;
#if USE_TEXT_LAYOUT_CACHE
- value = gTextLayoutCache.getValue(paint, text, 0, count, count, flags);
+ value = TextLayoutCache::getInstance().getValue(paint, text, 0, count, count, flags);
if (value == NULL) {
LOGE("Cannot get TextLayoutCache value");
return ;
@@ -507,7 +507,7 @@
#if RTL_USE_HARFBUZZ
sp<TextLayoutCacheValue> value;
#if USE_TEXT_LAYOUT_CACHE
- value = gTextLayoutCache.getValue(paint, text, start, count, contextCount, flags);
+ value = TextLayoutCache::getInstance().getValue(paint, text, start, count, contextCount, flags);
if (value == NULL) {
LOGE("Cannot get TextLayoutCache value");
return ;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 3fa4d48..16490da 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -87,9 +87,7 @@
<protected-broadcast android:name="android.bluetooth.device.action.NAME_FAILED" />
<protected-broadcast android:name="android.bluetooth.device.action.PAIRING_REQUEST" />
<protected-broadcast android:name="android.bluetooth.device.action.PAIRING_CANCEL" />
- <protected-broadcast android:name="android.bluetooth.device.action.CONNECTION_ACCESS_REQUEST" />
<protected-broadcast android:name="android.bluetooth.device.action.CONNECTION_ACCESS_REPLY" />
- <protected-broadcast android:name="android.bluetooth.device.action.CONNECTION_ACCESS_CANCEL" />
<protected-broadcast
android:name="android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED" />
<protected-broadcast
diff --git a/core/res/res/drawable-hdpi/list_divider_holo_dark.9.png b/core/res/res/drawable-hdpi/list_divider_holo_dark.9.png
deleted file mode 100644
index 7264a7a..0000000
--- a/core/res/res/drawable-hdpi/list_divider_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/list_divider_holo_light.9.png b/core/res/res/drawable-hdpi/list_divider_holo_light.9.png
deleted file mode 100644
index 74f48f5..0000000
--- a/core/res/res/drawable-hdpi/list_divider_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/list_longpressed_holo.9.png b/core/res/res/drawable-hdpi/list_longpressed_holo.9.png
index d06549c..4ea7afa 100644
--- a/core/res/res/drawable-hdpi/list_longpressed_holo.9.png
+++ b/core/res/res/drawable-hdpi/list_longpressed_holo.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/list_selected_holo_dark.9.png b/core/res/res/drawable-hdpi/list_selected_holo_dark.9.png
index dae40ca..e20b02d 100644
--- a/core/res/res/drawable-hdpi/list_selected_holo_dark.9.png
+++ b/core/res/res/drawable-hdpi/list_selected_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/list_selected_holo_light.9.png b/core/res/res/drawable-hdpi/list_selected_holo_light.9.png
index dae40ca..e20b02d 100644
--- a/core/res/res/drawable-hdpi/list_selected_holo_light.9.png
+++ b/core/res/res/drawable-hdpi/list_selected_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/list_divider_holo_dark.9.png b/core/res/res/drawable-mdpi/list_divider_holo_dark.9.png
deleted file mode 100644
index aa8015d..0000000
--- a/core/res/res/drawable-mdpi/list_divider_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/list_divider_holo_light.9.png b/core/res/res/drawable-mdpi/list_divider_holo_light.9.png
deleted file mode 100644
index c25c256..0000000
--- a/core/res/res/drawable-mdpi/list_divider_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/list_longpressed_holo.9.png b/core/res/res/drawable-mdpi/list_longpressed_holo.9.png
index 2b8a0b3..3bf8e03 100644
--- a/core/res/res/drawable-mdpi/list_longpressed_holo.9.png
+++ b/core/res/res/drawable-mdpi/list_longpressed_holo.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/list_selected_holo_dark.9.png b/core/res/res/drawable-mdpi/list_selected_holo_dark.9.png
index 4cbcee9..13cb131 100644
--- a/core/res/res/drawable-mdpi/list_selected_holo_dark.9.png
+++ b/core/res/res/drawable-mdpi/list_selected_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/list_selected_holo_light.9.png b/core/res/res/drawable-mdpi/list_selected_holo_light.9.png
index 4cbcee9..13cb131 100644
--- a/core/res/res/drawable-mdpi/list_selected_holo_light.9.png
+++ b/core/res/res/drawable-mdpi/list_selected_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-nodpi/indicator_code_lock_drag_direction_green_up.png b/core/res/res/drawable-nodpi/indicator_code_lock_drag_direction_green_up.png
deleted file mode 100644
index cc46f19..0000000
--- a/core/res/res/drawable-nodpi/indicator_code_lock_drag_direction_green_up.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-nodpi/indicator_code_lock_drag_direction_red_up.png b/core/res/res/drawable-nodpi/indicator_code_lock_drag_direction_red_up.png
deleted file mode 100644
index cc46f19..0000000
--- a/core/res/res/drawable-nodpi/indicator_code_lock_drag_direction_red_up.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/list_divider_horizontal_holo_dark.9.png b/core/res/res/drawable-xhdpi/list_divider_horizontal_holo_dark.9.png
index a8ad54d..0c901de 100644
--- a/core/res/res/drawable-xhdpi/list_divider_horizontal_holo_dark.9.png
+++ b/core/res/res/drawable-xhdpi/list_divider_horizontal_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/list_longpressed_holo.9.png b/core/res/res/drawable-xhdpi/list_longpressed_holo.9.png
index e303022..eda10e6 100644
--- a/core/res/res/drawable-xhdpi/list_longpressed_holo.9.png
+++ b/core/res/res/drawable-xhdpi/list_longpressed_holo.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/list_selected_holo_dark.9.png b/core/res/res/drawable-xhdpi/list_selected_holo_dark.9.png
index 4375032..ee5eb6f 100644
--- a/core/res/res/drawable-xhdpi/list_selected_holo_dark.9.png
+++ b/core/res/res/drawable-xhdpi/list_selected_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/list_selected_holo_light.9.png b/core/res/res/drawable-xhdpi/list_selected_holo_light.9.png
index 4375032..ee5eb6f 100644
--- a/core/res/res/drawable-xhdpi/list_selected_holo_light.9.png
+++ b/core/res/res/drawable-xhdpi/list_selected_holo_light.9.png
Binary files differ
diff --git a/core/res/res/values-bg-rBG/donottranslate-cldr.xml b/core/res/res/values-bg-rBG/donottranslate-cldr.xml
deleted file mode 100644
index 4c38ad2..0000000
--- a/core/res/res/values-bg-rBG/donottranslate-cldr.xml
+++ /dev/null
@@ -1,149 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="month_long_standalone_january">януари</string>
- <string name="month_long_standalone_february">февруари</string>
- <string name="month_long_standalone_march">март</string>
- <string name="month_long_standalone_april">април</string>
- <string name="month_long_standalone_may">май</string>
- <string name="month_long_standalone_june">юни</string>
- <string name="month_long_standalone_july">юли</string>
- <string name="month_long_standalone_august">август</string>
- <string name="month_long_standalone_september">септември</string>
- <string name="month_long_standalone_october">октомври</string>
- <string name="month_long_standalone_november">ноември</string>
- <string name="month_long_standalone_december">декември</string>
-
- <string name="month_long_january">януари</string>
- <string name="month_long_february">февруари</string>
- <string name="month_long_march">март</string>
- <string name="month_long_april">април</string>
- <string name="month_long_may">май</string>
- <string name="month_long_june">юни</string>
- <string name="month_long_july">юли</string>
- <string name="month_long_august">август</string>
- <string name="month_long_september">септември</string>
- <string name="month_long_october">октомври</string>
- <string name="month_long_november">ноември</string>
- <string name="month_long_december">декември</string>
-
- <string name="month_medium_january">ян.</string>
- <string name="month_medium_february">февр.</string>
- <string name="month_medium_march">март</string>
- <string name="month_medium_april">апр.</string>
- <string name="month_medium_may">май</string>
- <string name="month_medium_june">юни</string>
- <string name="month_medium_july">юли</string>
- <string name="month_medium_august">авг.</string>
- <string name="month_medium_september">септ.</string>
- <string name="month_medium_october">окт.</string>
- <string name="month_medium_november">ноем.</string>
- <string name="month_medium_december">дек.</string>
-
- <string name="month_shortest_january">я</string>
- <string name="month_shortest_february">ф</string>
- <string name="month_shortest_march">м</string>
- <string name="month_shortest_april">а</string>
- <string name="month_shortest_may">м</string>
- <string name="month_shortest_june">ю</string>
- <string name="month_shortest_july">ю</string>
- <string name="month_shortest_august">а</string>
- <string name="month_shortest_september">с</string>
- <string name="month_shortest_october">о</string>
- <string name="month_shortest_november">н</string>
- <string name="month_shortest_december">д</string>
-
- <string name="day_of_week_long_sunday">неделя</string>
- <string name="day_of_week_long_monday">понеделник</string>
- <string name="day_of_week_long_tuesday">вторник</string>
- <string name="day_of_week_long_wednesday">сряда</string>
- <string name="day_of_week_long_thursday">четвъртък</string>
- <string name="day_of_week_long_friday">петък</string>
- <string name="day_of_week_long_saturday">събота</string>
-
- <string name="day_of_week_medium_sunday">нд</string>
- <string name="day_of_week_medium_monday">пн</string>
- <string name="day_of_week_medium_tuesday">вт</string>
- <string name="day_of_week_medium_wednesday">ср</string>
- <string name="day_of_week_medium_thursday">чт</string>
- <string name="day_of_week_medium_friday">пт</string>
- <string name="day_of_week_medium_saturday">сб</string>
-
- <string name="day_of_week_short_sunday">нд</string>
- <string name="day_of_week_short_monday">пн</string>
- <string name="day_of_week_short_tuesday">вт</string>
- <string name="day_of_week_short_wednesday">ср</string>
- <string name="day_of_week_short_thursday">чт</string>
- <string name="day_of_week_short_friday">пт</string>
- <string name="day_of_week_short_saturday">сб</string>
-
- <string name="day_of_week_shortest_sunday">н</string>
- <string name="day_of_week_shortest_monday">п</string>
- <string name="day_of_week_shortest_tuesday">в</string>
- <string name="day_of_week_shortest_wednesday">с</string>
- <string name="day_of_week_shortest_thursday">ч</string>
- <string name="day_of_week_shortest_friday">п</string>
- <string name="day_of_week_shortest_saturday">с</string>
-
- <string name="am">пр. об.</string>
- <string name="pm">сл. об.</string>
- <string name="yesterday">Вчера</string>
- <string name="today">Днес</string>
- <string name="tomorrow">Утре</string>
-
- <string name="hour_minute_24">%-k:%M</string>
- <string name="hour_minute_ampm">%-l:%M %p</string>
- <string name="hour_minute_cap_ampm">%-l:%M %p</string>
- <string name="twelve_hour_time_format">h:mm a</string>
- <string name="twenty_four_hour_time_format">H:mm</string>
- <string name="numeric_date">%d.%m.%Y</string>
- <string name="numeric_date_format">dd.MM.yyyy</string>
- <string name="numeric_date_template">"%s.%s.%s"</string>
- <string name="month_day_year">%d %B %Y</string>
- <string name="time_of_day">%H:%M:%S</string>
- <string name="date_and_time">%H:%M:%S %d.%m.%Y</string>
- <string name="date_time">%2$s %1$s</string>
- <string name="time_date">%1$s %3$s</string>
- <string name="abbrev_month_day_year">%d.%m.%Y</string>
- <string name="month_day">%-e %B</string>
- <string name="month">%-B</string>
- <string name="month_year">%B %Y</string>
- <string name="abbrev_month_day">%-e %b</string>
- <string name="abbrev_month">%b</string>
- <string name="abbrev_month_year">%b %Y</string>
- <string name="time1_time2">%1$s-%2$s</string>
- <string name="date1_date2">%2$s - %5$s</string>
- <string name="numeric_md1_md2">%3$s.%2$s - %8$s.%7$s</string>
- <string name="numeric_wday1_md1_wday2_md2">%3$s.%2$s, %1$s - %8$s.%7$s, %6$s</string>
- <string name="numeric_mdy1_mdy2">%3$s.%2$s.%4$s - %8$s.%7$s.%9$s</string>
- <string name="numeric_wday1_mdy1_wday2_mdy2">%3$s.%2$s.%4$s, %1$s - %8$s.%7$s.%9$s, %6$s</string>
- <string name="numeric_wday1_mdy1_time1_wday2_mdy2_time2">%5$s %3$s.%2$s.%4$s, %1$s - %10$s %8$s.%7$s.%9$s, %6$s</string>
- <string name="numeric_md1_time1_md2_time2">%5$s %3$s.%2$s - %10$s %8$s.%7$s</string>
- <string name="numeric_wday1_md1_time1_wday2_md2_time2">%5$s %3$s.%2$s, %1$s - %10$s %8$s.%7$s, %6$s</string>
- <string name="numeric_mdy1_time1_mdy2_time2">%5$s %3$s.%2$s.%4$s - %10$s %8$s.%7$s.%9$s</string>
- <string name="wday1_date1_time1_wday2_date2_time2">%3$s %2$s, %1$s - %6$s %5$s, %4$s</string>
- <string name="wday1_date1_wday2_date2">%2$s, %1$s - %5$s, %4$s</string>
- <string name="date1_time1_date2_time2">%3$s %2$s - %6$s %5$s</string>
- <string name="time_wday_date">%1$s %3$s, %2$s</string>
- <string name="wday_date">%3$s, %2$s</string>
- <string name="time_wday">%1$s %2$s</string>
- <string name="same_year_md1_md2">%3$s %2$s - %8$s %7$s</string>
- <string name="same_year_wday1_md1_wday2_md2">%3$s %2$s, %1$s - %8$s %7$s, %6$s</string>
- <string name="same_year_md1_time1_md2_time2">%5$s %3$s %2$s - %10$s %8$s %7$s</string>
- <string name="same_month_md1_time1_md2_time2">%5$s %3$s %2$s - %10$s %8$s %7$s</string>
- <string name="same_year_wday1_md1_time1_wday2_md2_time2">%5$s %3$s %2$s, %1$s - %10$s %8$s %7$s, %6$s</string>
- <string name="same_month_wday1_md1_time1_wday2_md2_time2">%5$s %3$s %2$s, %1$s - %10$s %8$s %7$s, %6$s</string>
- <string name="same_year_mdy1_time1_mdy2_time2">%5$s %3$s %2$s %4$s - %10$s %8$s %7$s %9$s</string>
- <string name="same_month_mdy1_time1_mdy2_time2">%5$s %3$s %2$s %4$s - %10$s %8$s %7$s %9$s</string>
- <string name="same_year_wday1_mdy1_time1_wday2_mdy2_time2">%5$s %3$s %2$s %4$s, %1$s - %10$s %8$s %7$s %9$s, %6$s</string>
- <string name="same_month_wday1_mdy1_time1_wday2_mdy2_time2">%5$s %3$s %2$s %4$s, %1$s - %10$s %8$s %7$s %9$s, %6$s</string>
- <string name="same_month_wday1_mdy1_wday2_mdy2">%3$s %2$s %4$s, %1$s - %8$s %7$s %9$s, %6$s</string>
- <string name="same_month_md1_md2">%3$s-%8$s %2$s</string>
- <string name="same_month_wday1_md1_wday2_md2">%3$s %2$s, %1$s - %8$s %7$s, %6$s</string>
- <string name="same_year_mdy1_mdy2">%3$s %2$s - %8$s %7$s %9$s</string>
- <string name="same_month_mdy1_mdy2">%3$s-%8$s %2$s %9$s</string>
- <string name="same_year_wday1_mdy1_wday2_mdy2">%3$s %2$s %9$s, %1$s - %8$s %7$s y, %6$s</string>
- <string name="short_format_month">%b</string>
- <string name="full_wday_month_day_no_year">EEEE MMMM d</string>
- <string name="abbrev_wday_month_day_year">d MMM yyyy, E</string>
-</resources>
diff --git a/core/res/res/values-bg/donottranslate-cldr.xml b/core/res/res/values-bg/donottranslate-cldr.xml
index 62f550a..dc8b3ad 100644
--- a/core/res/res/values-bg/donottranslate-cldr.xml
+++ b/core/res/res/values-bg/donottranslate-cldr.xml
@@ -128,22 +128,22 @@
<string name="wday_date">%3$s, %2$s</string>
<string name="time_wday">%1$s %2$s</string>
<string name="same_year_md1_md2">%3$s %2$s - %8$s %7$s</string>
- <string name="same_year_wday1_md1_wday2_md2">%1$s %2$s %3$s - %6$s %7$s %8$s</string>
+ <string name="same_year_wday1_md1_wday2_md2">%3$s %2$s, %1$s - %8$s %7$s, %6$s</string>
<string name="same_year_md1_time1_md2_time2">%5$s %3$s %2$s - %10$s %8$s %7$s</string>
<string name="same_month_md1_time1_md2_time2">%5$s %3$s %2$s - %10$s %8$s %7$s</string>
- <string name="same_year_wday1_md1_time1_wday2_md2_time2">%5$s %1$s %2$s %3$s - %10$s %6$s %7$s %8$s</string>
- <string name="same_month_wday1_md1_time1_wday2_md2_time2">%5$s %1$s %2$s %3$s - %10$s %6$s %7$s %8$s</string>
+ <string name="same_year_wday1_md1_time1_wday2_md2_time2">%5$s %3$s %2$s, %1$s - %10$s %8$s %7$s, %6$s</string>
+ <string name="same_month_wday1_md1_time1_wday2_md2_time2">%5$s %3$s %2$s, %1$s - %10$s %8$s %7$s, %6$s</string>
<string name="same_year_mdy1_time1_mdy2_time2">%5$s %3$s %2$s %4$s - %10$s %8$s %7$s %9$s</string>
<string name="same_month_mdy1_time1_mdy2_time2">%5$s %3$s %2$s %4$s - %10$s %8$s %7$s %9$s</string>
<string name="same_year_wday1_mdy1_time1_wday2_mdy2_time2">%5$s %3$s %2$s %4$s, %1$s - %10$s %8$s %7$s %9$s, %6$s</string>
<string name="same_month_wday1_mdy1_time1_wday2_mdy2_time2">%5$s %3$s %2$s %4$s, %1$s - %10$s %8$s %7$s %9$s, %6$s</string>
<string name="same_month_wday1_mdy1_wday2_mdy2">%3$s %2$s %4$s, %1$s - %8$s %7$s %9$s, %6$s</string>
<string name="same_month_md1_md2">%3$s-%8$s %2$s</string>
- <string name="same_month_wday1_md1_wday2_md2">%1$s %2$s %3$s - %6$s %7$s %8$s</string>
+ <string name="same_month_wday1_md1_wday2_md2">%3$s %2$s, %1$s - %8$s %7$s, %6$s</string>
<string name="same_year_mdy1_mdy2">%3$s %2$s - %8$s %7$s %9$s</string>
<string name="same_month_mdy1_mdy2">%3$s-%8$s %2$s %9$s</string>
<string name="same_year_wday1_mdy1_wday2_mdy2">%3$s %2$s %9$s, %1$s - %8$s %7$s y, %6$s</string>
<string name="short_format_month">%b</string>
- <string name="full_wday_month_day_no_year">E, d MMMM</string>
- <string name="abbrev_wday_month_day_year">d MMM y, E</string>
+ <string name="full_wday_month_day_no_year">d MMMM, EEEE</string>
+ <string name="abbrev_wday_month_day_year">d MMM yyyy, E</string>
</resources>
diff --git a/data/etc/android.hardware.wifi.direct.xml b/data/etc/android.hardware.wifi.direct.xml
new file mode 100644
index 0000000..78cb474
--- /dev/null
+++ b/data/etc/android.hardware.wifi.direct.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- This is the standard feature indicating that the device includes WiFi Direct. -->
+<permissions>
+ <feature name="android.hardware.wifi.direct" />
+</permissions>
diff --git a/data/fonts/DroidSansFallback.ttf b/data/fonts/DroidSansFallback.ttf
index ba9d76f..ff97670 100644
--- a/data/fonts/DroidSansFallback.ttf
+++ b/data/fonts/DroidSansFallback.ttf
Binary files differ
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index 72d233a3..0a3deb1 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -290,6 +290,8 @@
/**
* Implement this interface if you want to create an drawable that is RTL aware
+ *
+ * @hide
*/
public static interface Callback2 extends Callback {
/**
@@ -379,6 +381,8 @@
/**
* Use the current {@link android.graphics.drawable.Drawable.Callback2} implementation to get
* the resolved layout direction of this Drawable.
+ *
+ * @hide
*/
public int getResolvedLayoutDirectionSelf() {
final Callback callback = getCallback();
diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp
index 7e8c7fd..349b9e3 100644
--- a/libs/hwui/LayerRenderer.cpp
+++ b/libs/hwui/LayerRenderer.cpp
@@ -91,6 +91,13 @@
#endif
}
+// TODO: This implementation is flawed and can generate T-junctions
+// in the mesh, which will in turn produce cracks when the
+// mesh is rotated/skewed. The easiest way to fix this would
+// be, for each row, to add new vertices shared with the previous
+// row when the two rows share an edge.
+// In practice, T-junctions do not appear often so this has yet
+// to be fixed.
void LayerRenderer::generateMesh() {
#if RENDER_LAYERS_AS_REGIONS
if (mLayer->region.isRect() || mLayer->region.isEmpty()) {
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index a20a88e..24784af 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -724,6 +724,8 @@
return;
}
+ // TODO: See LayerRenderer.cpp::generateMesh() for important
+ // information about this implementation
if (!layer->region.isEmpty()) {
size_t count;
const android::Rect* rects = layer->region.getArray(&count);
diff --git a/media/java/android/media/videoeditor/MediaArtistNativeHelper.java b/media/java/android/media/videoeditor/MediaArtistNativeHelper.java
index aa0a2e9..d7b8eaa 100644
--- a/media/java/android/media/videoeditor/MediaArtistNativeHelper.java
+++ b/media/java/android/media/videoeditor/MediaArtistNativeHelper.java
@@ -3758,65 +3758,33 @@
/**
* This method extracts a frame from the input file
- * and returns the frame as a bitmap
- *
- * @param inputFile The inputFile
- * @param width The width of the output frame
- * @param height The height of the output frame
- * @param timeMS The time in ms at which the frame has to be extracted
+ * and returns the frame as a bitmap. See getPixelsList() for more information.
*/
- Bitmap getPixels(String inputFile, int width, int height, long timeMS) {
- if (inputFile == null) {
- throw new IllegalArgumentException("Invalid input file");
- }
-
- /* Make width and height as even */
- final int newWidth = (width + 1) & 0xFFFFFFFE;
- final int newHeight = (height + 1) & 0xFFFFFFFE;
-
- /* Create a temp bitmap for resized thumbnails */
- Bitmap tempBitmap = null;
- if ((newWidth != width) || (newHeight != height)) {
- tempBitmap = Bitmap.createBitmap(newWidth, newHeight, Bitmap.Config.ARGB_8888);
- }
-
- IntBuffer rgb888 = IntBuffer.allocate(newWidth * newHeight * 4);
- Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
- nativeGetPixels(inputFile, rgb888.array(), newWidth, newHeight, timeMS);
-
- if ((newWidth == width) && (newHeight == height)) {
- bitmap.copyPixelsFromBuffer(rgb888);
- } else {
- /* Create a temp bitmap to be used for resize */
- tempBitmap.copyPixelsFromBuffer(rgb888);
-
- /* Create a canvas to resize */
- final Canvas canvas = new Canvas(bitmap);
- canvas.drawBitmap(tempBitmap, new Rect(0, 0, newWidth, newHeight),
- new Rect(0, 0, width, height), sResizePaint);
- canvas.setBitmap(null);
- }
-
- if (tempBitmap != null) {
- tempBitmap.recycle();
- }
-
- return bitmap;
+ Bitmap getPixels(String filename, int width, int height, long timeMs,
+ int videoRotation) {
+ final Bitmap result[] = new Bitmap[1];
+ getPixelsList(filename, width, height, timeMs, timeMs, 1, new int[] {0},
+ new MediaItem.GetThumbnailListCallback() {
+ public void onThumbnail(Bitmap bitmap, int index) {
+ result[0] = bitmap;
+ }
+ }, videoRotation);
+ return result[0];
}
/**
* This method extracts a list of frame from the
* input file and returns the frame in bitmap array
*
- * @param filename The inputFile
- * @param width The width of the output frame
- * @param height The height of the output frame
+ * @param filename The input file name
+ * @param width The width of the output frame, before rotation
+ * @param height The height of the output frame, before rotation
* @param startMs The starting time in ms
* @param endMs The end time in ms
* @param thumbnailCount The number of frames to be extracted
* @param indices The indices of thumbnails wanted
* @param callback The callback used to pass back the bitmaps
- * from startMs to endMs
+ * @param videoRotation The rotation degree need to be done for the bitmap
*
* @return The frames as bitmaps in bitmap array
**/
@@ -3824,62 +3792,69 @@
long startMs, long endMs, int thumbnailCount, int[] indices,
final MediaItem.GetThumbnailListCallback callback,
final int videoRotation) {
- /* Make width and height as even */
- final int newWidth = (width + 1) & 0xFFFFFFFE;
- final int newHeight = (height + 1) & 0xFFFFFFFE;
- final int thumbnailSize = newWidth * newHeight;
- /* Create a temp bitmap for resized thumbnails */
- final Bitmap tempBitmap =
- (newWidth != width || newHeight != height)
- ? Bitmap.createBitmap(newWidth, newHeight, Bitmap.Config.ARGB_8888)
+ // The decoder needs output width and height as even
+ final int decWidth = (width + 1) & 0xFFFFFFFE;
+ final int decHeight = (height + 1) & 0xFFFFFFFE;
+ final int thumbnailSize = decWidth * decHeight;
+
+ // We convert the decoder output (in int[]) to a bitmap by first
+ // copy it into an IntBuffer, then use Bitmap.copyPixelsFromBuffer to
+ // copy it to the bitmap.
+ final int[] decArray = new int[thumbnailSize];
+ final IntBuffer decBuffer = IntBuffer.allocate(thumbnailSize);
+
+ // If we need to resize and/or rotate the decoder output, we need a
+ // temporary bitmap to hold the decoded output.
+ final boolean needToMassage =
+ (decWidth != width || decHeight != height || videoRotation != 0);
+ final Bitmap tmpBitmap = needToMassage
+ ? Bitmap.createBitmap(decWidth, decHeight, Bitmap.Config.ARGB_8888)
: null;
- final int[] rgb888 = new int[thumbnailSize];
- final IntBuffer tmpBuffer = IntBuffer.allocate(thumbnailSize);
- nativeGetPixelsList(filename, rgb888, newWidth, newHeight,
- thumbnailCount, videoRotation, startMs, endMs, indices,
+ // The final output bitmap width/height may swap because of rotation.
+ final boolean needToSwapWH = (videoRotation == 90 || videoRotation == 270);
+ final int outWidth = needToSwapWH ? height : width;
+ final int outHeight = needToSwapWH ? width : height;
+
+ nativeGetPixelsList(filename, decArray, decWidth, decHeight,
+ thumbnailCount, startMs, endMs, indices,
new NativeGetPixelsListCallback() {
public void onThumbnail(int index) {
- Bitmap bitmap = Bitmap.createBitmap(
- width, height, Bitmap.Config.ARGB_8888);
- tmpBuffer.put(rgb888, 0, thumbnailSize);
- tmpBuffer.rewind();
+ // This is the bitmap we will output to the client
+ Bitmap outBitmap = Bitmap.createBitmap(
+ outWidth, outHeight, Bitmap.Config.ARGB_8888);
- if ((newWidth == width) && (newHeight == height)) {
- bitmap.copyPixelsFromBuffer(tmpBuffer);
+ // Copy int[] to IntBuffer
+ decBuffer.put(decArray, 0, thumbnailSize);
+ decBuffer.rewind();
+
+ if (!needToMassage) {
+ // We can directly read the decoded result to output bitmap
+ outBitmap.copyPixelsFromBuffer(decBuffer);
} else {
- /* Copy the out rgb buffer to temp bitmap */
- tempBitmap.copyPixelsFromBuffer(tmpBuffer);
+ // Copy the decoded result to an intermediate bitmap first
+ tmpBitmap.copyPixelsFromBuffer(decBuffer);
- /* Create a canvas to resize */
- final Canvas canvas = new Canvas(bitmap);
- canvas.drawBitmap(tempBitmap,
- new Rect(0, 0, newWidth, newHeight),
- new Rect(0, 0, width, height), sResizePaint);
-
- canvas.setBitmap(null);
+ // Create a canvas to resize/rotate the bitmap
+ // First scale the decoded bitmap to (0,0)-(1,1), rotate it
+ // with (0.5, 0.5) as center, then scale it to
+ // (outWidth, outHeight).
+ final Canvas canvas = new Canvas(outBitmap);
+ Matrix m = new Matrix();
+ float sx = 1f / decWidth;
+ float sy = 1f / decHeight;
+ m.postScale(sx, sy);
+ m.postRotate(videoRotation, 0.5f, 0.5f);
+ m.postScale(outWidth, outHeight);
+ canvas.drawBitmap(tmpBitmap, m, sResizePaint);
}
-
- if (videoRotation == 0) {
- callback.onThumbnail(bitmap, index);
- } else {
- Matrix mtx = new Matrix();
- mtx.postRotate(videoRotation);
- Bitmap rotatedBmp =
- Bitmap.createBitmap(bitmap, 0, 0, width, height, mtx, false);
- callback.onThumbnail(rotatedBmp, index);
-
- if (bitmap != null) {
- bitmap.recycle();
- }
- }
-
+ callback.onThumbnail(outBitmap, index);
}
});
- if (tempBitmap != null) {
- tempBitmap.recycle();
+ if (tmpBitmap != null) {
+ tmpBitmap.recycle();
}
}
@@ -3996,7 +3971,7 @@
long timeMS);
private native int nativeGetPixelsList(String fileName, int[] pixelArray,
- int width, int height, int nosofTN, int videoRotation, long startTimeMs,
+ int width, int height, int nosofTN, long startTimeMs,
long endTimeMs, int[] indices, NativeGetPixelsListCallback callback);
/**
diff --git a/media/java/android/media/videoeditor/MediaImageItem.java b/media/java/android/media/videoeditor/MediaImageItem.java
index 65a9e19..a862d00 100755
--- a/media/java/android/media/videoeditor/MediaImageItem.java
+++ b/media/java/android/media/videoeditor/MediaImageItem.java
@@ -606,7 +606,7 @@
public Bitmap getThumbnail(int width, int height, long timeMs) throws IOException {
if (getGeneratedImageClip() != null) {
return mMANativeHelper.getPixels(getGeneratedImageClip(),
- width, height,timeMs);
+ width, height, timeMs, 0);
} else {
return scaleImage(mFilename, width, height);
}
diff --git a/media/java/android/media/videoeditor/MediaVideoItem.java b/media/java/android/media/videoeditor/MediaVideoItem.java
index 2ce857c..bbcdf57 100755
--- a/media/java/android/media/videoeditor/MediaVideoItem.java
+++ b/media/java/android/media/videoeditor/MediaVideoItem.java
@@ -293,7 +293,14 @@
throw new IllegalArgumentException("Invalid Dimensions");
}
- return mMANativeHelper.getPixels(super.getFilename(), width, height,timeMs);
+ if (mVideoRotationDegree == 90 || mVideoRotationDegree == 270) {
+ int temp = width;
+ width = height;
+ height = temp;
+ }
+
+ return mMANativeHelper.getPixels(
+ getFilename(), width, height, timeMs, mVideoRotationDegree);
}
/*
@@ -318,8 +325,14 @@
throw new IllegalArgumentException("Invalid dimension");
}
- mMANativeHelper.getPixelsList(super.getFilename(), width,
- height, startMs, endMs, thumbnailCount, indices, callback,
+ if (mVideoRotationDegree == 90 || mVideoRotationDegree == 270) {
+ int temp = width;
+ width = height;
+ height = temp;
+ }
+
+ mMANativeHelper.getPixelsList(getFilename(), width, height,
+ startMs, endMs, thumbnailCount, indices, callback,
mVideoRotationDegree);
}
diff --git a/media/java/android/media/videoeditor/VideoEditorImpl.java b/media/java/android/media/videoeditor/VideoEditorImpl.java
index f18dd88..2446c2f 100755
--- a/media/java/android/media/videoeditor/VideoEditorImpl.java
+++ b/media/java/android/media/videoeditor/VideoEditorImpl.java
@@ -1825,27 +1825,10 @@
if (mMediaItems.size() > 0) {
MediaItem mI = mMediaItems.get(0);
/*
- * Lets initialize the width for default aspect ratio i.e 16:9
+ * Keep aspect ratio of the image
*/
int height = 480;
- int width = 854;
- switch (mI.getAspectRatio()) {
- case MediaProperties.ASPECT_RATIO_3_2:
- width = 720;
- break;
- case MediaProperties.ASPECT_RATIO_4_3:
- width = 640;
- break;
- case MediaProperties.ASPECT_RATIO_5_3:
- width = 800;
- break;
- case MediaProperties.ASPECT_RATIO_11_9:
- width = 586;
- break;
- case MediaProperties.ASPECT_RATIO_16_9:
- case MediaProperties.ASPECT_RATIO_UNDEFINED:
- break;
- }
+ int width = mI.getWidth() * height / mI.getHeight();
Bitmap projectBitmap = null;
String filename = mI.getFilename();
diff --git a/media/jni/mediaeditor/VideoEditorMain.cpp b/media/jni/mediaeditor/VideoEditorMain.cpp
index 4e73581..4e954fc 100755
--- a/media/jni/mediaeditor/VideoEditorMain.cpp
+++ b/media/jni/mediaeditor/VideoEditorMain.cpp
@@ -185,7 +185,6 @@
M4OSA_UInt32 width,
M4OSA_UInt32 height,
M4OSA_UInt32 noOfThumbnails,
- M4OSA_UInt32 videoRotation,
jlong startTime,
jlong endTime,
jintArray indexArray,
@@ -292,7 +291,7 @@
(void *)videoEditor_release },
{"nativeGetPixels", "(Ljava/lang/String;[IIIJ)I",
(void*)videoEditor_getPixels },
- {"nativeGetPixelsList", "(Ljava/lang/String;[IIIIIJJ[ILandroid/media/videoeditor/MediaArtistNativeHelper$NativeGetPixelsListCallback;)I",
+ {"nativeGetPixelsList", "(Ljava/lang/String;[IIIIJJ[ILandroid/media/videoeditor/MediaArtistNativeHelper$NativeGetPixelsListCallback;)I",
(void*)videoEditor_getPixelsList },
{"getMediaProperties",
"(Ljava/lang/String;)Landroid/media/videoeditor/MediaArtistNativeHelper$Properties;",
@@ -2286,7 +2285,6 @@
M4OSA_UInt32 width,
M4OSA_UInt32 height,
M4OSA_UInt32 noOfThumbnails,
- M4OSA_UInt32 videoRotation,
jlong startTime,
jlong endTime,
jintArray indexArray,
diff --git a/media/libmedia/IMediaPlayer.cpp b/media/libmedia/IMediaPlayer.cpp
index bd89ad8..50a41ca 100644
--- a/media/libmedia/IMediaPlayer.cpp
+++ b/media/libmedia/IMediaPlayer.cpp
@@ -108,6 +108,7 @@
Parcel data, reply;
data.writeInterfaceToken(IMediaPlayer::getInterfaceDescriptor());
data.writeStrongBinder(source->asBinder());
+ remote()->transact(SET_DATA_SOURCE_STREAM, data, &reply);
return reply.readInt32();
}
diff --git a/media/libstagefright/tests/SurfaceMediaSource_test.cpp b/media/libstagefright/tests/SurfaceMediaSource_test.cpp
index d663602..d7bb703 100644
--- a/media/libstagefright/tests/SurfaceMediaSource_test.cpp
+++ b/media/libstagefright/tests/SurfaceMediaSource_test.cpp
@@ -156,8 +156,6 @@
eglDestroySurface(mEglDisplay, mEglSurface);
}
if (mEglDisplay != EGL_NO_DISPLAY) {
- eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE,
- EGL_NO_CONTEXT);
eglTerminate(mEglDisplay);
}
ASSERT_EQ(EGL_SUCCESS, eglGetError());
@@ -461,6 +459,7 @@
// The following call dequeues and queues the buffer
eglSwapBuffers(mEglDisplay, mEglSurface);
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
glDisable(GL_SCISSOR_TEST);
}
@@ -796,7 +795,12 @@
LOGV("framesCount = %d", nFramesCount);
}
- ASSERT_EQ(NO_ERROR, native_window_api_disconnect(mANW.get(), NATIVE_WINDOW_API_EGL));
+ EXPECT_TRUE(eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE,
+ EGL_NO_CONTEXT));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ eglDestroySurface(mEglDisplay, mEglSurface);
+ mEglSurface = EGL_NO_SURFACE;
+
writer.stop();
}
// Test to examine whether we can render GL buffers in to the surface
@@ -875,7 +879,12 @@
LOGV("framesCount = %d", nFramesCount);
}
- ASSERT_EQ(NO_ERROR, native_window_api_disconnect(mANW.get(), NATIVE_WINDOW_API_EGL));
+ EXPECT_TRUE(eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE,
+ EGL_NO_CONTEXT));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ eglDestroySurface(mEglDisplay, mEglSurface);
+ mEglSurface = EGL_NO_SURFACE;
+
LOGV("Stopping MediaRecorder...");
CHECK_EQ(OK, mr->stop());
mr.clear();
@@ -913,7 +922,12 @@
LOGV("framesCount = %d", nFramesCount);
}
- ASSERT_EQ(NO_ERROR, native_window_api_disconnect(mANW.get(), NATIVE_WINDOW_API_EGL));
+ EXPECT_TRUE(eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE,
+ EGL_NO_CONTEXT));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ eglDestroySurface(mEglDisplay, mEglSurface);
+ mEglSurface = EGL_NO_SURFACE;
+
LOGV("Stopping MediaRecorder...");
CHECK_EQ(OK, mr->stop());
mr.clear();
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index 997318a..be2ef82 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -163,6 +163,10 @@
private static final int MSG_FULL_CONFIRMATION_TIMEOUT = 9;
private static final int MSG_RUN_FULL_RESTORE = 10;
+ // backup task state machine tick
+ static final int MSG_BACKUP_RESTORE_STEP = 20;
+ static final int MSG_OP_COMPLETE = 21;
+
// Timeout interval for deciding that a bind or clear-data has taken too long
static final long TIMEOUT_INTERVAL = 10 * 1000;
@@ -344,7 +348,16 @@
static final int OP_ACKNOWLEDGED = 1;
static final int OP_TIMEOUT = -1;
- final SparseIntArray mCurrentOperations = new SparseIntArray();
+ class Operation {
+ public int state;
+ public BackupRestoreTask callback;
+
+ Operation(int initialState, BackupRestoreTask callbackObj) {
+ state = initialState;
+ callback = callbackObj;
+ }
+ }
+ final SparseArray<Operation> mCurrentOperations = new SparseArray<Operation>();
final Object mCurrentOpLock = new Object();
final Random mTokenGenerator = new Random();
@@ -442,13 +455,16 @@
}
}
+ // At this point, we have started a new journal file, and the old
+ // file identity is being passed to the backup processing task.
+ // When it completes successfully, that old journal file will be
+ // deleted. If we crash prior to that, the old journal is parsed
+ // at next boot and the journaled requests fulfilled.
if (queue.size() > 0) {
- // At this point, we have started a new journal file, and the old
- // file identity is being passed to the backup processing thread.
- // When it completes successfully, that old journal file will be
- // deleted. If we crash prior to that, the old journal is parsed
- // at next boot and the journaled requests fulfilled.
- (new PerformBackupTask(transport, queue, oldJournal)).run();
+ // Spin up a backup state sequence and set it running
+ PerformBackupTask pbt = new PerformBackupTask(transport, queue, oldJournal);
+ Message pbtMessage = obtainMessage(MSG_BACKUP_RESTORE_STEP, pbt);
+ sendMessage(pbtMessage);
} else {
Slog.v(TAG, "Backup requested but nothing pending");
mWakelock.release();
@@ -456,6 +472,29 @@
break;
}
+ case MSG_BACKUP_RESTORE_STEP:
+ {
+ try {
+ BackupRestoreTask task = (BackupRestoreTask) msg.obj;
+ if (MORE_DEBUG) Slog.v(TAG, "Got next step for " + task + ", executing");
+ task.execute();
+ } catch (ClassCastException e) {
+ Slog.e(TAG, "Invalid backup task in flight, obj=" + msg.obj);
+ }
+ break;
+ }
+
+ case MSG_OP_COMPLETE:
+ {
+ try {
+ BackupRestoreTask task = (BackupRestoreTask) msg.obj;
+ task.operationComplete();
+ } catch (ClassCastException e) {
+ Slog.e(TAG, "Invalid completion in flight, obj=" + msg.obj);
+ }
+ break;
+ }
+
case MSG_RUN_FULL_BACKUP:
{
FullBackupParams params = (FullBackupParams)msg.obj;
@@ -469,9 +508,12 @@
{
RestoreParams params = (RestoreParams)msg.obj;
Slog.d(TAG, "MSG_RUN_RESTORE observer=" + params.observer);
- (new PerformRestoreTask(params.transport, params.observer,
+ PerformRestoreTask task = new PerformRestoreTask(
+ params.transport, params.observer,
params.token, params.pkgInfo, params.pmToken,
- params.needFullBackup, params.filterSet)).run();
+ params.needFullBackup, params.filterSet);
+ Message restoreMsg = obtainMessage(MSG_BACKUP_RESTORE_STEP, task);
+ sendMessage(restoreMsg);
break;
}
@@ -540,15 +582,7 @@
case MSG_TIMEOUT:
{
- synchronized (mCurrentOpLock) {
- final int token = msg.arg1;
- int state = mCurrentOperations.get(token, OP_TIMEOUT);
- if (state == OP_PENDING) {
- if (DEBUG) Slog.v(TAG, "TIMEOUT: token=" + token);
- mCurrentOperations.put(token, OP_TIMEOUT);
- }
- mCurrentOpLock.notifyAll();
- }
+ handleTimeout(msg.arg1, msg.obj);
break;
}
@@ -558,7 +592,7 @@
if (mActiveRestoreSession != null) {
// Client app left the restore session dangling. We know that it
// can't be in the middle of an actual restore operation because
- // those are executed serially on this same handler thread. Clean
+ // the timeout is suspended while a restore is in progress. Clean
// up now.
Slog.w(TAG, "Restore session timed out; aborting");
post(mActiveRestoreSession.new EndRestoreRunnable(
@@ -1113,12 +1147,14 @@
}
// Enqueue a new backup of every participant
- int N = mBackupParticipants.size();
- for (int i=0; i<N; i++) {
- int uid = mBackupParticipants.keyAt(i);
- HashSet<ApplicationInfo> participants = mBackupParticipants.valueAt(i);
- for (ApplicationInfo app: participants) {
- dataChangedImpl(app.packageName);
+ synchronized (mBackupParticipants) {
+ int N = mBackupParticipants.size();
+ for (int i=0; i<N; i++) {
+ int uid = mBackupParticipants.keyAt(i);
+ HashSet<ApplicationInfo> participants = mBackupParticipants.valueAt(i);
+ for (ApplicationInfo app: participants) {
+ dataChangedImpl(app.packageName);
+ }
}
}
}
@@ -1588,50 +1624,120 @@
}
// -----
- // Utility methods used by the asynchronous-with-timeout backup/restore operations
- boolean waitUntilOperationComplete(int token) {
- int finalState = OP_PENDING;
+ // Interface and methods used by the asynchronous-with-timeout backup/restore operations
+
+ interface BackupRestoreTask {
+ // Execute one tick of whatever state machine the task implements
+ void execute();
+
+ // An operation that wanted a callback has completed
+ void operationComplete();
+
+ // An operation that wanted a callback has timed out
+ void handleTimeout();
+ }
+
+ void prepareOperationTimeout(int token, long interval, BackupRestoreTask callback) {
+ if (MORE_DEBUG) Slog.v(TAG, "starting timeout: token=" + Integer.toHexString(token)
+ + " interval=" + interval);
synchronized (mCurrentOpLock) {
- try {
- while ((finalState = mCurrentOperations.get(token, OP_TIMEOUT)) == OP_PENDING) {
- try {
- mCurrentOpLock.wait();
- } catch (InterruptedException e) {}
+ mCurrentOperations.put(token, new Operation(OP_PENDING, callback));
+
+ Message msg = mBackupHandler.obtainMessage(MSG_TIMEOUT, token, 0, callback);
+ mBackupHandler.sendMessageDelayed(msg, interval);
+ }
+ }
+
+ // synchronous waiter case
+ boolean waitUntilOperationComplete(int token) {
+ if (MORE_DEBUG) Slog.i(TAG, "Blocking until operation complete for "
+ + Integer.toHexString(token));
+ int finalState = OP_PENDING;
+ Operation op = null;
+ synchronized (mCurrentOpLock) {
+ while (true) {
+ op = mCurrentOperations.get(token);
+ if (op == null) {
+ // mysterious disappearance: treat as success with no callback
+ break;
+ } else {
+ if (op.state == OP_PENDING) {
+ try {
+ mCurrentOpLock.wait();
+ } catch (InterruptedException e) {}
+ // When the wait is notified we loop around and recheck the current state
+ } else {
+ // No longer pending; we're done
+ finalState = op.state;
+ break;
+ }
}
- } catch (IndexOutOfBoundsException e) {
- // the operation has been mysteriously cleared from our
- // bookkeeping -- consider this a success and ignore it.
}
}
+
mBackupHandler.removeMessages(MSG_TIMEOUT);
if (MORE_DEBUG) Slog.v(TAG, "operation " + Integer.toHexString(token)
+ " complete: finalState=" + finalState);
return finalState == OP_ACKNOWLEDGED;
}
- void prepareOperationTimeout(int token, long interval) {
- if (MORE_DEBUG) Slog.v(TAG, "starting timeout: token=" + Integer.toHexString(token)
- + " interval=" + interval);
+ void handleTimeout(int token, Object obj) {
+ // Notify any synchronous waiters
+ Operation op = null;
synchronized (mCurrentOpLock) {
- mCurrentOperations.put(token, OP_PENDING);
- Message msg = mBackupHandler.obtainMessage(MSG_TIMEOUT, token, 0);
- mBackupHandler.sendMessageDelayed(msg, interval);
+ op = mCurrentOperations.get(token);
+ if (MORE_DEBUG) {
+ if (op == null) Slog.w(TAG, "Timeout of token " + Integer.toHexString(token)
+ + " but no op found");
+ }
+ int state = (op != null) ? op.state : OP_TIMEOUT;
+ if (state == OP_PENDING) {
+ if (DEBUG) Slog.v(TAG, "TIMEOUT: token=" + Integer.toHexString(token));
+ op.state = OP_TIMEOUT;
+ mCurrentOperations.put(token, op);
+ }
+ mCurrentOpLock.notifyAll();
+ }
+
+ // If there's a TimeoutHandler for this event, call it
+ if (op != null && op.callback != null) {
+ op.callback.handleTimeout();
}
}
// ----- Back up a set of applications via a worker thread -----
- class PerformBackupTask implements Runnable {
- private static final String TAG = "PerformBackupThread";
+ enum BackupState {
+ INITIAL,
+ RUNNING_QUEUE,
+ FINAL
+ }
+
+ class PerformBackupTask implements BackupRestoreTask {
+ private static final String TAG = "PerformBackupTask";
+
IBackupTransport mTransport;
ArrayList<BackupRequest> mQueue;
+ ArrayList<BackupRequest> mOriginalQueue;
File mStateDir;
File mJournal;
+ BackupState mCurrentState;
+
+ // carried information about the current in-flight operation
+ PackageInfo mCurrentPackage;
+ File mSavedStateName;
+ File mBackupDataName;
+ File mNewStateName;
+ ParcelFileDescriptor mSavedState;
+ ParcelFileDescriptor mBackupData;
+ ParcelFileDescriptor mNewState;
+ int mStatus;
+ boolean mFinished;
public PerformBackupTask(IBackupTransport transport, ArrayList<BackupRequest> queue,
File journal) {
mTransport = transport;
- mQueue = queue;
+ mOriginalQueue = queue;
mJournal = journal;
try {
@@ -1639,26 +1745,62 @@
} catch (RemoteException e) {
// can't happen; the transport is local
}
+
+ mCurrentState = BackupState.INITIAL;
+ mFinished = false;
}
- public void run() {
- int status = BackupConstants.TRANSPORT_OK;
- long startRealtime = SystemClock.elapsedRealtime();
+ // Main entry point: perform one chunk of work, updating the state as appropriate
+ // and reposting the next chunk to the primary backup handler thread.
+ @Override
+ public void execute() {
+ switch (mCurrentState) {
+ case INITIAL:
+ beginBackup();
+ break;
+
+ case RUNNING_QUEUE:
+ invokeNextAgent();
+ break;
+
+ case FINAL:
+ if (!mFinished) finalizeBackup();
+ else {
+ Slog.e(TAG, "Duplicate finish");
+ }
+ mFinished = true;
+ break;
+ }
+ }
+
+ // We're starting a backup pass. Initialize the transport and send
+ // the PM metadata blob if we haven't already.
+ void beginBackup() {
+ mStatus = BackupConstants.TRANSPORT_OK;
+
+ // Sanity check: if the queue is empty we have no work to do.
+ if (mOriginalQueue.isEmpty()) {
+ Slog.w(TAG, "Backup begun with an empty queue - nothing to do.");
+ return;
+ }
+
+ // We need to retain the original queue contents in case of transport
+ // failure, but we want a working copy that we can manipulate along
+ // the way.
+ mQueue = (ArrayList<BackupRequest>) mOriginalQueue.clone();
+
if (DEBUG) Slog.v(TAG, "Beginning backup of " + mQueue.size() + " targets");
- // Backups run at background priority
- Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
-
+ File pmState = new File(mStateDir, PACKAGE_MANAGER_SENTINEL);
try {
EventLog.writeEvent(EventLogTags.BACKUP_START, mTransport.transportDirName());
// If we haven't stored package manager metadata yet, we must init the transport.
- File pmState = new File(mStateDir, PACKAGE_MANAGER_SENTINEL);
- if (status == BackupConstants.TRANSPORT_OK && pmState.length() <= 0) {
+ if (mStatus == BackupConstants.TRANSPORT_OK && pmState.length() <= 0) {
Slog.i(TAG, "Initializing (wiping) backup state and transport storage");
resetBackupState(mStateDir); // Just to make sure.
- status = mTransport.initializeDevice();
- if (status == BackupConstants.TRANSPORT_OK) {
+ mStatus = mTransport.initializeDevice();
+ if (mStatus == BackupConstants.TRANSPORT_OK) {
EventLog.writeEvent(EventLogTags.BACKUP_INITIALIZE);
} else {
EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, "(initialize)");
@@ -1671,207 +1813,219 @@
// directly and use a synthetic BackupRequest. We always run this pass
// because it's cheap and this way we guarantee that we don't get out of
// step even if we're selecting among various transports at run time.
- if (status == BackupConstants.TRANSPORT_OK) {
+ if (mStatus == BackupConstants.TRANSPORT_OK) {
PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent(
mPackageManager, allAgentPackages());
- status = processOneBackup(PACKAGE_MANAGER_SENTINEL,
+ mStatus = invokeAgentForBackup(PACKAGE_MANAGER_SENTINEL,
IBackupAgent.Stub.asInterface(pmAgent.onBind()), mTransport);
}
- if (status == BackupConstants.TRANSPORT_OK) {
- // Now run all the backups in our queue
- status = doQueuedBackups(mTransport);
- }
-
- if (status == BackupConstants.TRANSPORT_OK) {
- // Tell the transport to finish everything it has buffered
- status = mTransport.finishBackup();
- if (status == BackupConstants.TRANSPORT_OK) {
- int millis = (int) (SystemClock.elapsedRealtime() - startRealtime);
- EventLog.writeEvent(EventLogTags.BACKUP_SUCCESS, mQueue.size(), millis);
- } else {
- EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, "(finish)");
- Slog.e(TAG, "Transport error in finishBackup()");
- }
- }
-
- if (status == BackupConstants.TRANSPORT_NOT_INITIALIZED) {
- // The backend reports that our dataset has been wiped. We need to
- // reset all of our bookkeeping and instead run a new backup pass for
- // everything.
+ if (mStatus == BackupConstants.TRANSPORT_NOT_INITIALIZED) {
+ // The backend reports that our dataset has been wiped. Note this in
+ // the event log; the no-success code below will reset the backup
+ // state as well.
EventLog.writeEvent(EventLogTags.BACKUP_RESET, mTransport.transportDirName());
- resetBackupState(mStateDir);
}
} catch (Exception e) {
Slog.e(TAG, "Error in backup thread", e);
- status = BackupConstants.TRANSPORT_ERROR;
+ mStatus = BackupConstants.TRANSPORT_ERROR;
} finally {
- // If everything actually went through and this is the first time we've
- // done a backup, we can now record what the current backup dataset token
- // is.
- if ((mCurrentToken == 0) && (status == BackupConstants.TRANSPORT_OK)) {
- try {
- mCurrentToken = mTransport.getCurrentRestoreSet();
- } catch (RemoteException e) { /* cannot happen */ }
- writeRestoreTokens();
+ // If we've succeeded so far, invokeAgentForBackup() will have run the PM
+ // metadata and its completion/timeout callback will continue the state
+ // machine chain. If it failed that won't happen; we handle that now.
+ if (mStatus != BackupConstants.TRANSPORT_OK) {
+ // if things went wrong at this point, we need to
+ // restage everything and try again later.
+ resetBackupState(mStateDir); // Just to make sure.
+ executeNextState(BackupState.FINAL);
}
-
- // If things went wrong, we need to re-stage the apps we had expected
- // to be backing up in this pass. This journals the package names in
- // the current active pending-backup file, not in the we are holding
- // here in mJournal.
- if (status != BackupConstants.TRANSPORT_OK) {
- Slog.w(TAG, "Backup pass unsuccessful, restaging");
- for (BackupRequest req : mQueue) {
- dataChangedImpl(req.packageName);
- }
-
- // We also want to reset the backup schedule based on whatever
- // the transport suggests by way of retry/backoff time.
- try {
- startBackupAlarmsLocked(mTransport.requestBackupTime());
- } catch (RemoteException e) { /* cannot happen */ }
- }
-
- // Either backup was successful, in which case we of course do not need
- // this pass's journal any more; or it failed, in which case we just
- // re-enqueued all of these packages in the current active journal.
- // Either way, we no longer need this pass's journal.
- if (mJournal != null && !mJournal.delete()) {
- Slog.e(TAG, "Unable to remove backup journal file " + mJournal);
- }
-
- // Only once we're entirely finished do we release the wakelock
- if (status == BackupConstants.TRANSPORT_NOT_INITIALIZED) {
- backupNow();
- }
-
- mWakelock.release();
}
}
- private int doQueuedBackups(IBackupTransport transport) {
- for (BackupRequest request : mQueue) {
- Slog.d(TAG, "starting agent for backup of " + request);
+ // Transport has been initialized and the PM metadata submitted successfully
+ // if that was warranted. Now we process the single next thing in the queue.
+ void invokeNextAgent() {
+ mStatus = BackupConstants.TRANSPORT_OK;
- // Verify that the requested app exists; it might be something that
- // requested a backup but was then uninstalled. The request was
- // journalled and rather than tamper with the journal it's safer
- // to sanity-check here. This also gives us the classname of the
- // package's backup agent.
- PackageInfo pkg;
- try {
- pkg = mPackageManager.getPackageInfo(request.packageName, 0);
- } catch (NameNotFoundException e) {
- Slog.d(TAG, "Package does not exist; skipping");
- continue;
- }
+ // Sanity check that we have work to do. If not, skip to the end where
+ // we reestablish the wakelock invariants etc.
+ if (mQueue.isEmpty()) {
+ Slog.e(TAG, "Running queue but it's empty!");
+ executeNextState(BackupState.FINAL);
+ return;
+ }
+
+ // pop the entry we're going to process on this step
+ BackupRequest request = mQueue.get(0);
+ mQueue.remove(0);
+
+ Slog.d(TAG, "starting agent for backup of " + request);
+
+ // Verify that the requested app exists; it might be something that
+ // requested a backup but was then uninstalled. The request was
+ // journalled and rather than tamper with the journal it's safer
+ // to sanity-check here. This also gives us the classname of the
+ // package's backup agent.
+ try {
+ mCurrentPackage = mPackageManager.getPackageInfo(request.packageName,
+ PackageManager.GET_SIGNATURES);
IBackupAgent agent = null;
try {
- mWakelock.setWorkSource(new WorkSource(pkg.applicationInfo.uid));
- agent = bindToAgentSynchronous(pkg.applicationInfo,
+ mWakelock.setWorkSource(new WorkSource(mCurrentPackage.applicationInfo.uid));
+ agent = bindToAgentSynchronous(mCurrentPackage.applicationInfo,
IApplicationThread.BACKUP_MODE_INCREMENTAL);
if (agent != null) {
- int result = processOneBackup(request.packageName, agent, transport);
- if (result != BackupConstants.TRANSPORT_OK) return result;
+ mStatus = invokeAgentForBackup(request.packageName, agent, mTransport);
+ // at this point we'll either get a completion callback from the
+ // agent, or a timeout message on the main handler. either way, we're
+ // done here as long as we're successful so far.
+ } else {
+ // Timeout waiting for the agent
+ mStatus = BackupConstants.AGENT_ERROR;
}
} catch (SecurityException ex) {
// Try for the next one.
Slog.d(TAG, "error in bind/backup", ex);
- } finally {
- try { // unbind even on timeout, just in case
- mActivityManager.unbindBackupAgent(pkg.applicationInfo);
- } catch (RemoteException e) {}
+ mStatus = BackupConstants.AGENT_ERROR;
+ }
+ } catch (NameNotFoundException e) {
+ Slog.d(TAG, "Package does not exist; skipping");
+ } finally {
+ mWakelock.setWorkSource(null);
+
+ // If there was an agent error, no timeout/completion handling will occur.
+ // That means we need to deal with the next state ourselves.
+ if (mStatus != BackupConstants.TRANSPORT_OK) {
+ BackupState nextState = BackupState.RUNNING_QUEUE;
+
+ // An agent-level failure means we reenqueue this one agent for
+ // a later retry, but otherwise proceed normally.
+ if (mStatus == BackupConstants.AGENT_ERROR) {
+ if (MORE_DEBUG) Slog.i(TAG, "Agent failure for " + request.packageName
+ + " - restaging");
+ dataChangedImpl(request.packageName);
+ mStatus = BackupConstants.TRANSPORT_OK;
+ if (mQueue.isEmpty()) nextState = BackupState.FINAL;
+ } else if (mStatus != BackupConstants.TRANSPORT_OK) {
+ // Transport-level failure means we reenqueue everything
+ revertAndEndBackup();
+ nextState = BackupState.FINAL;
+ }
+
+ executeNextState(nextState);
}
}
-
- mWakelock.setWorkSource(null);
-
- return BackupConstants.TRANSPORT_OK;
}
- private int processOneBackup(String packageName, IBackupAgent agent,
+ void finalizeBackup() {
+ // Either backup was successful, in which case we of course do not need
+ // this pass's journal any more; or it failed, in which case we just
+ // re-enqueued all of these packages in the current active journal.
+ // Either way, we no longer need this pass's journal.
+ if (mJournal != null && !mJournal.delete()) {
+ Slog.e(TAG, "Unable to remove backup journal file " + mJournal);
+ }
+
+ // If everything actually went through and this is the first time we've
+ // done a backup, we can now record what the current backup dataset token
+ // is.
+ if ((mCurrentToken == 0) && (mStatus == BackupConstants.TRANSPORT_OK)) {
+ try {
+ mCurrentToken = mTransport.getCurrentRestoreSet();
+ } catch (RemoteException e) {} // can't happen
+ writeRestoreTokens();
+ }
+
+ // Set up the next backup pass
+ if (mStatus == BackupConstants.TRANSPORT_NOT_INITIALIZED) {
+ backupNow();
+ }
+
+ // Only once we're entirely finished do we release the wakelock
+ Slog.i(TAG, "Backup pass finished.");
+ mWakelock.release();
+ }
+
+ // Invoke an agent's doBackup() and start a timeout message spinning on the main
+ // handler in case it doesn't get back to us.
+ int invokeAgentForBackup(String packageName, IBackupAgent agent,
IBackupTransport transport) {
if (DEBUG) Slog.d(TAG, "processOneBackup doBackup() on " + packageName);
- File savedStateName = new File(mStateDir, packageName);
- File backupDataName = new File(mDataDir, packageName + ".data");
- File newStateName = new File(mStateDir, packageName + ".new");
+ mSavedStateName = new File(mStateDir, packageName);
+ mBackupDataName = new File(mDataDir, packageName + ".data");
+ mNewStateName = new File(mStateDir, packageName + ".new");
- ParcelFileDescriptor savedState = null;
- ParcelFileDescriptor backupData = null;
- ParcelFileDescriptor newState = null;
+ mSavedState = null;
+ mBackupData = null;
+ mNewState = null;
- PackageInfo packInfo;
final int token = generateToken();
try {
// Look up the package info & signatures. This is first so that if it
// throws an exception, there's no file setup yet that would need to
// be unraveled.
if (packageName.equals(PACKAGE_MANAGER_SENTINEL)) {
- // The metadata 'package' is synthetic
- packInfo = new PackageInfo();
- packInfo.packageName = packageName;
- } else {
- packInfo = mPackageManager.getPackageInfo(packageName,
- PackageManager.GET_SIGNATURES);
+ // The metadata 'package' is synthetic; construct one and make
+ // sure our global state is pointed at it
+ mCurrentPackage = new PackageInfo();
+ mCurrentPackage.packageName = packageName;
}
// In a full backup, we pass a null ParcelFileDescriptor as
// the saved-state "file". This is by definition an incremental,
// so we build a saved state file to pass.
- savedState = ParcelFileDescriptor.open(savedStateName,
+ mSavedState = ParcelFileDescriptor.open(mSavedStateName,
ParcelFileDescriptor.MODE_READ_ONLY |
ParcelFileDescriptor.MODE_CREATE); // Make an empty file if necessary
- backupData = ParcelFileDescriptor.open(backupDataName,
+ mBackupData = ParcelFileDescriptor.open(mBackupDataName,
ParcelFileDescriptor.MODE_READ_WRITE |
ParcelFileDescriptor.MODE_CREATE |
ParcelFileDescriptor.MODE_TRUNCATE);
- newState = ParcelFileDescriptor.open(newStateName,
+ mNewState = ParcelFileDescriptor.open(mNewStateName,
ParcelFileDescriptor.MODE_READ_WRITE |
ParcelFileDescriptor.MODE_CREATE |
ParcelFileDescriptor.MODE_TRUNCATE);
// Initiate the target's backup pass
- prepareOperationTimeout(token, TIMEOUT_BACKUP_INTERVAL);
- agent.doBackup(savedState, backupData, newState, token, mBackupManagerBinder);
- boolean success = waitUntilOperationComplete(token);
-
- if (!success) {
- // timeout -- bail out into the failed-transaction logic
- throw new RuntimeException("Backup timeout");
- }
-
- logBackupComplete(packageName);
- if (DEBUG) Slog.v(TAG, "doBackup() success");
+ prepareOperationTimeout(token, TIMEOUT_BACKUP_INTERVAL, this);
+ agent.doBackup(mSavedState, mBackupData, mNewState, token, mBackupManagerBinder);
} catch (Exception e) {
- Slog.e(TAG, "Error backing up " + packageName, e);
- EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, packageName, e.toString());
- backupDataName.delete();
- newStateName.delete();
- return BackupConstants.TRANSPORT_ERROR;
- } finally {
- try { if (savedState != null) savedState.close(); } catch (IOException e) {}
- try { if (backupData != null) backupData.close(); } catch (IOException e) {}
- try { if (newState != null) newState.close(); } catch (IOException e) {}
- savedState = backupData = newState = null;
- synchronized (mCurrentOpLock) {
- mCurrentOperations.clear();
- }
+ Slog.e(TAG, "Error invoking for backup on " + packageName);
+ EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, packageName,
+ e.toString());
+ agentErrorCleanup();
+ return BackupConstants.AGENT_ERROR;
}
- // Now propagate the newly-backed-up data to the transport
- int result = BackupConstants.TRANSPORT_OK;
+ // At this point the agent is off and running. The next thing to happen will
+ // either be a callback from the agent, at which point we'll process its data
+ // for transport, or a timeout. Either way the next phase will happen in
+ // response to the TimeoutHandler interface callbacks.
+ return BackupConstants.TRANSPORT_OK;
+ }
+
+ @Override
+ public void operationComplete() {
+ // Okay, the agent successfully reported back to us. Spin the data off to the
+ // transport and proceed with the next stage.
+ if (MORE_DEBUG) Slog.v(TAG, "operationComplete(): sending data to transport for "
+ + mCurrentPackage.packageName);
+ mBackupHandler.removeMessages(MSG_TIMEOUT);
+ clearAgentState();
+
+ ParcelFileDescriptor backupData = null;
+ mStatus = BackupConstants.TRANSPORT_OK;
try {
- int size = (int) backupDataName.length();
+ int size = (int) mBackupDataName.length();
if (size > 0) {
- if (result == BackupConstants.TRANSPORT_OK) {
- backupData = ParcelFileDescriptor.open(backupDataName,
+ if (mStatus == BackupConstants.TRANSPORT_OK) {
+ backupData = ParcelFileDescriptor.open(mBackupDataName,
ParcelFileDescriptor.MODE_READ_ONLY);
- result = transport.performBackup(packInfo, backupData);
+ mStatus = mTransport.performBackup(mCurrentPackage, backupData);
}
// TODO - We call finishBackup() for each application backed up, because
@@ -1879,8 +2033,8 @@
// hold off on finishBackup() until the end, which implies holding off on
// renaming *all* the output state files (see below) until that happens.
- if (result == BackupConstants.TRANSPORT_OK) {
- result = transport.finishBackup();
+ if (mStatus == BackupConstants.TRANSPORT_OK) {
+ mStatus = mTransport.finishBackup();
}
} else {
if (DEBUG) Slog.i(TAG, "no backup data written; not calling transport");
@@ -1889,22 +2043,102 @@
// After successful transport, delete the now-stale data
// and juggle the files so that next time we supply the agent
// with the new state file it just created.
- if (result == BackupConstants.TRANSPORT_OK) {
- backupDataName.delete();
- newStateName.renameTo(savedStateName);
- EventLog.writeEvent(EventLogTags.BACKUP_PACKAGE, packageName, size);
+ if (mStatus == BackupConstants.TRANSPORT_OK) {
+ mBackupDataName.delete();
+ mNewStateName.renameTo(mSavedStateName);
+ EventLog.writeEvent(EventLogTags.BACKUP_PACKAGE,
+ mCurrentPackage.packageName, size);
+ logBackupComplete(mCurrentPackage.packageName);
} else {
- EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, packageName);
+ EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE,
+ mCurrentPackage.packageName);
}
} catch (Exception e) {
- Slog.e(TAG, "Transport error backing up " + packageName, e);
- EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, packageName);
- result = BackupConstants.TRANSPORT_ERROR;
+ Slog.e(TAG, "Transport error backing up " + mCurrentPackage.packageName, e);
+ EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE,
+ mCurrentPackage.packageName);
+ mStatus = BackupConstants.TRANSPORT_ERROR;
} finally {
try { if (backupData != null) backupData.close(); } catch (IOException e) {}
}
- return result;
+ // If we encountered an error here it's a transport-level failure. That
+ // means we need to halt everything and reschedule everything for next time.
+ final BackupState nextState;
+ if (mStatus != BackupConstants.TRANSPORT_OK) {
+ revertAndEndBackup();
+ nextState = BackupState.FINAL;
+ } else {
+ // Success! Proceed with the next app if any, otherwise we're done.
+ nextState = (mQueue.isEmpty()) ? BackupState.FINAL : BackupState.RUNNING_QUEUE;
+ }
+
+ executeNextState(nextState);
+ }
+
+ @Override
+ public void handleTimeout() {
+ // Whoops, the current agent timed out running doBackup(). Tidy up and restage
+ // it for the next time we run a backup pass.
+ // !!! TODO: keep track of failure counts per agent, and blacklist those which
+ // fail repeatedly (i.e. have proved themselves to be buggy).
+ Slog.e(TAG, "Timeout backing up " + mCurrentPackage.packageName);
+ EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, mCurrentPackage.packageName,
+ "timeout");
+ agentErrorCleanup();
+ dataChangedImpl(mCurrentPackage.packageName);
+ }
+
+ void revertAndEndBackup() {
+ if (MORE_DEBUG) Slog.i(TAG, "Reverting backup queue - restaging everything");
+ for (BackupRequest request : mOriginalQueue) {
+ dataChangedImpl(request.packageName);
+ }
+ // We also want to reset the backup schedule based on whatever
+ // the transport suggests by way of retry/backoff time.
+ restartBackupAlarm();
+ }
+
+ void agentErrorCleanup() {
+ mBackupDataName.delete();
+ mNewStateName.delete();
+ clearAgentState();
+
+ executeNextState(mQueue.isEmpty() ? BackupState.FINAL : BackupState.RUNNING_QUEUE);
+ }
+
+ // Cleanup common to both success and failure cases
+ void clearAgentState() {
+ try { if (mSavedState != null) mSavedState.close(); } catch (IOException e) {}
+ try { if (mBackupData != null) mBackupData.close(); } catch (IOException e) {}
+ try { if (mNewState != null) mNewState.close(); } catch (IOException e) {}
+ mSavedState = mBackupData = mNewState = null;
+ synchronized (mCurrentOpLock) {
+ mCurrentOperations.clear();
+ }
+
+ // If this was a pseudopackage there's no associated Activity Manager state
+ if (mCurrentPackage.applicationInfo != null) {
+ try { // unbind even on timeout, just in case
+ mActivityManager.unbindBackupAgent(mCurrentPackage.applicationInfo);
+ } catch (RemoteException e) {}
+ }
+ }
+
+ void restartBackupAlarm() {
+ synchronized (mQueueLock) {
+ try {
+ startBackupAlarmsLocked(mTransport.requestBackupTime());
+ } catch (RemoteException e) { /* cannot happen */ }
+ }
+ }
+
+ void executeNextState(BackupState nextState) {
+ if (MORE_DEBUG) Slog.i(TAG, " => executing next step on "
+ + this + " nextState=" + nextState);
+ mCurrentState = nextState;
+ Message msg = mBackupHandler.obtainMessage(MSG_BACKUP_RESTORE_STEP, this);
+ mBackupHandler.sendMessage(msg);
}
}
@@ -1959,7 +2193,7 @@
}
if (DEBUG) Slog.d(TAG, "Calling doFullBackup() on " + mPackage.packageName);
- prepareOperationTimeout(mToken, TIMEOUT_FULL_BACKUP_INTERVAL);
+ prepareOperationTimeout(mToken, TIMEOUT_FULL_BACKUP_INTERVAL, null);
mAgent.doFullBackup(mPipe, mToken, mBackupManagerBinder);
} catch (IOException e) {
Slog.e(TAG, "Error running full backup for " + mPackage.packageName);
@@ -2320,7 +2554,7 @@
sendOnBackupPackage("Shared storage");
final int token = generateToken();
- prepareOperationTimeout(token, TIMEOUT_SHARED_BACKUP_INTERVAL);
+ prepareOperationTimeout(token, TIMEOUT_SHARED_BACKUP_INTERVAL, null);
agent.doFullBackup(mOutputFile, token, mBackupManagerBinder);
if (!waitUntilOperationComplete(token)) {
Slog.e(TAG, "Full backup failed on shared storage");
@@ -2899,8 +3133,7 @@
try {
if (DEBUG) Slog.d(TAG, "Invoking agent to restore file "
+ info.path);
- prepareOperationTimeout(token,
- TIMEOUT_FULL_BACKUP_INTERVAL);
+ prepareOperationTimeout(token, TIMEOUT_FULL_BACKUP_INTERVAL, null);
// fire up the app's agent listening on the socket. If
// the agent is running in the system process we can't
// just invoke it asynchronously, so we provide a thread
@@ -3693,7 +3926,15 @@
return true;
}
- class PerformRestoreTask implements Runnable {
+ enum RestoreState {
+ INITIAL,
+ DOWNLOAD_DATA,
+ PM_METADATA,
+ RUNNING_QUEUE,
+ FINAL
+ }
+
+ class PerformRestoreTask implements BackupRestoreTask {
private IBackupTransport mTransport;
private IRestoreObserver mObserver;
private long mToken;
@@ -3702,6 +3943,21 @@
private int mPmToken;
private boolean mNeedFullBackup;
private HashSet<String> mFilterSet;
+ private long mStartRealtime;
+ private PackageManagerBackupAgent mPmAgent;
+ private List<PackageInfo> mAgentPackages;
+ private ArrayList<PackageInfo> mRestorePackages;
+ private RestoreState mCurrentState;
+ private int mCount;
+ private boolean mFinished;
+ private int mStatus;
+ private File mBackupDataName;
+ private File mNewStateName;
+ private File mSavedStateName;
+ private ParcelFileDescriptor mBackupData;
+ private ParcelFileDescriptor mNewState;
+ private PackageInfo mCurrentPackage;
+
class RestoreRequest {
public PackageInfo app;
@@ -3716,6 +3972,10 @@
PerformRestoreTask(IBackupTransport transport, IRestoreObserver observer,
long restoreSetToken, PackageInfo targetPackage, int pmToken,
boolean needFullBackup, String[] filterSet) {
+ mCurrentState = RestoreState.INITIAL;
+ mFinished = false;
+ mPmAgent = null;
+
mTransport = transport;
mObserver = observer;
mToken = restoreSetToken;
@@ -3739,50 +3999,79 @@
}
}
- public void run() {
- long startRealtime = SystemClock.elapsedRealtime();
- if (DEBUG) Slog.v(TAG, "Beginning restore process mTransport=" + mTransport
- + " mObserver=" + mObserver + " mToken=" + Long.toHexString(mToken)
- + " mTargetPackage=" + mTargetPackage + " mFilterSet=" + mFilterSet
- + " mPmToken=" + mPmToken);
+ // Execute one tick of whatever state machine the task implements
+ @Override
+ public void execute() {
+ if (MORE_DEBUG) Slog.v(TAG, "*** Executing restore step: " + mCurrentState);
+ switch (mCurrentState) {
+ case INITIAL:
+ beginRestore();
+ break;
- PackageManagerBackupAgent pmAgent = null;
- int error = -1; // assume error
+ case DOWNLOAD_DATA:
+ downloadRestoreData();
+ break;
- // build the set of apps to restore
+ case PM_METADATA:
+ restorePmMetadata();
+ break;
+
+ case RUNNING_QUEUE:
+ restoreNextAgent();
+ break;
+
+ case FINAL:
+ if (!mFinished) finalizeRestore();
+ else {
+ Slog.e(TAG, "Duplicate finish");
+ }
+ mFinished = true;
+ break;
+ }
+ }
+
+ // Initialize and set up for the PM metadata restore, which comes first
+ void beginRestore() {
+ // Don't account time doing the restore as inactivity of the app
+ // that has opened a restore session.
+ mBackupHandler.removeMessages(MSG_RESTORE_TIMEOUT);
+
+ // Assume error until we successfully init everything
+ mStatus = BackupConstants.TRANSPORT_ERROR;
+
try {
// TODO: Log this before getAvailableRestoreSets, somehow
EventLog.writeEvent(EventLogTags.RESTORE_START, mTransport.transportDirName(), mToken);
// Get the list of all packages which have backup enabled.
// (Include the Package Manager metadata pseudo-package first.)
- ArrayList<PackageInfo> restorePackages = new ArrayList<PackageInfo>();
+ mRestorePackages = new ArrayList<PackageInfo>();
PackageInfo omPackage = new PackageInfo();
omPackage.packageName = PACKAGE_MANAGER_SENTINEL;
- restorePackages.add(omPackage);
+ mRestorePackages.add(omPackage);
- List<PackageInfo> agentPackages = allAgentPackages();
+ mAgentPackages = allAgentPackages();
if (mTargetPackage == null) {
// if there's a filter set, strip out anything that isn't
// present before proceeding
if (mFilterSet != null) {
- for (int i = agentPackages.size() - 1; i >= 0; i--) {
- final PackageInfo pkg = agentPackages.get(i);
+ for (int i = mAgentPackages.size() - 1; i >= 0; i--) {
+ final PackageInfo pkg = mAgentPackages.get(i);
if (! mFilterSet.contains(pkg.packageName)) {
- agentPackages.remove(i);
+ mAgentPackages.remove(i);
}
}
- if (DEBUG) {
+ if (MORE_DEBUG) {
Slog.i(TAG, "Post-filter package set for restore:");
- for (PackageInfo p : agentPackages) {
+ for (PackageInfo p : mAgentPackages) {
Slog.i(TAG, " " + p);
}
}
}
- restorePackages.addAll(agentPackages);
+ mRestorePackages.addAll(mAgentPackages);
} else {
// Just one package to attempt restore of
- restorePackages.add(mTargetPackage);
+ mRestorePackages.add(mTargetPackage);
}
// let the observer know that we're running
@@ -3790,306 +4079,412 @@
try {
// !!! TODO: get an actual count from the transport after
// its startRestore() runs?
- mObserver.restoreStarting(restorePackages.size());
+ mObserver.restoreStarting(mRestorePackages.size());
} catch (RemoteException e) {
Slog.d(TAG, "Restore observer died at restoreStarting");
mObserver = null;
}
}
+ } catch (RemoteException e) {
+ // Something has gone catastrophically wrong with the transport
+ Slog.e(TAG, "Error communicating with transport for restore");
+ executeNextState(RestoreState.FINAL);
+ return;
+ }
- if (mTransport.startRestore(mToken, restorePackages.toArray(new PackageInfo[0])) !=
- BackupConstants.TRANSPORT_OK) {
+ mStatus = BackupConstants.TRANSPORT_OK;
+ executeNextState(RestoreState.DOWNLOAD_DATA);
+ }
+
+ void downloadRestoreData() {
+ // Note that the download phase can be very time consuming, but we're executing
+ // it inline here on the looper. This is "okay" because it is not calling out to
+ // third party code; the transport is "trusted," and so we assume it is being a
+ // good citizen and timing out etc when appropriate.
+ //
+ // TODO: when appropriate, move the download off the looper and rearrange the
+ // error handling around that.
+ try {
+ mStatus = mTransport.startRestore(mToken,
+ mRestorePackages.toArray(new PackageInfo[0]));
+ if (mStatus != BackupConstants.TRANSPORT_OK) {
Slog.e(TAG, "Error starting restore operation");
EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
+ executeNextState(RestoreState.FINAL);
return;
}
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error communicating with transport for restore");
+ EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
+ mStatus = BackupConstants.TRANSPORT_ERROR;
+ executeNextState(RestoreState.FINAL);
+ return;
+ }
+ // Successful download of the data to be parceled out to the apps, so off we go.
+ executeNextState(RestoreState.PM_METADATA);
+ }
+
+ void restorePmMetadata() {
+ try {
String packageName = mTransport.nextRestorePackage();
if (packageName == null) {
Slog.e(TAG, "Error getting first restore package");
EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
+ mStatus = BackupConstants.TRANSPORT_ERROR;
+ executeNextState(RestoreState.FINAL);
return;
} else if (packageName.equals("")) {
Slog.i(TAG, "No restore data available");
- int millis = (int) (SystemClock.elapsedRealtime() - startRealtime);
+ int millis = (int) (SystemClock.elapsedRealtime() - mStartRealtime);
EventLog.writeEvent(EventLogTags.RESTORE_SUCCESS, 0, millis);
+ mStatus = BackupConstants.TRANSPORT_OK;
+ executeNextState(RestoreState.FINAL);
return;
} else if (!packageName.equals(PACKAGE_MANAGER_SENTINEL)) {
Slog.e(TAG, "Expected restore data for \"" + PACKAGE_MANAGER_SENTINEL
- + "\", found only \"" + packageName + "\"");
+ + "\", found only \"" + packageName + "\"");
EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, PACKAGE_MANAGER_SENTINEL,
"Package manager data missing");
+ executeNextState(RestoreState.FINAL);
return;
}
// Pull the Package Manager metadata from the restore set first
- pmAgent = new PackageManagerBackupAgent(
- mPackageManager, agentPackages);
- processOneRestore(omPackage, 0, IBackupAgent.Stub.asInterface(pmAgent.onBind()),
+ PackageInfo omPackage = new PackageInfo();
+ omPackage.packageName = PACKAGE_MANAGER_SENTINEL;
+ mPmAgent = new PackageManagerBackupAgent(
+ mPackageManager, mAgentPackages);
+ initiateOneRestore(omPackage, 0, IBackupAgent.Stub.asInterface(mPmAgent.onBind()),
mNeedFullBackup);
+ // The PM agent called operationComplete() already, because our invocation
+ // of it is process-local and therefore synchronous. That means that a
+ // RUNNING_QUEUE message is already enqueued. Only if we're unable to
+ // proceed with running the queue do we remove that pending message and
+ // jump straight to the FINAL state.
// Verify that the backup set includes metadata. If not, we can't do
// signature/version verification etc, so we simply do not proceed with
// the restore operation.
- if (!pmAgent.hasMetadata()) {
+ if (!mPmAgent.hasMetadata()) {
Slog.e(TAG, "No restore metadata available, so not restoring settings");
EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, PACKAGE_MANAGER_SENTINEL,
- "Package manager restore metadata missing");
+ "Package manager restore metadata missing");
+ mStatus = BackupConstants.TRANSPORT_ERROR;
+ mBackupHandler.removeMessages(MSG_BACKUP_RESTORE_STEP, this);
+ executeNextState(RestoreState.FINAL);
return;
}
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error communicating with transport for restore");
+ EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
+ mStatus = BackupConstants.TRANSPORT_ERROR;
+ mBackupHandler.removeMessages(MSG_BACKUP_RESTORE_STEP, this);
+ executeNextState(RestoreState.FINAL);
+ return;
+ }
- int count = 0;
- for (;;) {
- packageName = mTransport.nextRestorePackage();
+ // Metadata is intact, so we can now run the restore queue. If we get here,
+ // we have already enqueued the necessary next-step message on the looper.
+ }
- if (packageName == null) {
- Slog.e(TAG, "Error getting next restore package");
- EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
- return;
- } else if (packageName.equals("")) {
- if (DEBUG) Slog.v(TAG, "No next package, finishing restore");
- break;
- }
+ void restoreNextAgent() {
+ try {
+ String packageName = mTransport.nextRestorePackage();
- if (mObserver != null) {
- try {
- mObserver.onUpdate(count, packageName);
- } catch (RemoteException e) {
- Slog.d(TAG, "Restore observer died in onUpdate");
- mObserver = null;
- }
- }
-
- Metadata metaInfo = pmAgent.getRestoredMetadata(packageName);
- if (metaInfo == null) {
- Slog.e(TAG, "Missing metadata for " + packageName);
- EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
- "Package metadata missing");
- continue;
- }
-
- PackageInfo packageInfo;
- try {
- int flags = PackageManager.GET_SIGNATURES;
- packageInfo = mPackageManager.getPackageInfo(packageName, flags);
- } catch (NameNotFoundException e) {
- Slog.e(TAG, "Invalid package restoring data", e);
- EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
- "Package missing on device");
- continue;
- }
-
- if (metaInfo.versionCode > packageInfo.versionCode) {
- // Data is from a "newer" version of the app than we have currently
- // installed. If the app has not declared that it is prepared to
- // handle this case, we do not attempt the restore.
- if ((packageInfo.applicationInfo.flags
- & ApplicationInfo.FLAG_RESTORE_ANY_VERSION) == 0) {
- String message = "Version " + metaInfo.versionCode
- + " > installed version " + packageInfo.versionCode;
- Slog.w(TAG, "Package " + packageName + ": " + message);
- EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE,
- packageName, message);
- continue;
- } else {
- if (DEBUG) Slog.v(TAG, "Version " + metaInfo.versionCode
- + " > installed " + packageInfo.versionCode
- + " but restoreAnyVersion");
- }
- }
-
- if (!signaturesMatch(metaInfo.signatures, packageInfo)) {
- Slog.w(TAG, "Signature mismatch restoring " + packageName);
- EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
- "Signature mismatch");
- continue;
- }
-
- if (DEBUG) Slog.v(TAG, "Package " + packageName
- + " restore version [" + metaInfo.versionCode
- + "] is compatible with installed version ["
- + packageInfo.versionCode + "]");
-
- // Then set up and bind the agent
- IBackupAgent agent = bindToAgentSynchronous(
- packageInfo.applicationInfo,
- IApplicationThread.BACKUP_MODE_INCREMENTAL);
- if (agent == null) {
- Slog.w(TAG, "Can't find backup agent for " + packageName);
- EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
- "Restore agent missing");
- continue;
- }
-
- // And then finally run the restore on this agent
- try {
- processOneRestore(packageInfo, metaInfo.versionCode, agent,
- mNeedFullBackup);
- ++count;
- } finally {
- // unbind and tidy up even on timeout or failure, just in case
- mActivityManager.unbindBackupAgent(packageInfo.applicationInfo);
-
- // The agent was probably running with a stub Application object,
- // which isn't a valid run mode for the main app logic. Shut
- // down the app so that next time it's launched, it gets the
- // usual full initialization. Note that this is only done for
- // full-system restores: when a single app has requested a restore,
- // it is explicitly not killed following that operation.
- if (mTargetPackage == null && (packageInfo.applicationInfo.flags
- & ApplicationInfo.FLAG_KILL_AFTER_RESTORE) != 0) {
- if (DEBUG) Slog.d(TAG, "Restore complete, killing host process of "
- + packageInfo.applicationInfo.processName);
- mActivityManager.killApplicationProcess(
- packageInfo.applicationInfo.processName,
- packageInfo.applicationInfo.uid);
- }
- }
- }
-
- // if we get this far, report success to the observer
- error = 0;
- int millis = (int) (SystemClock.elapsedRealtime() - startRealtime);
- EventLog.writeEvent(EventLogTags.RESTORE_SUCCESS, count, millis);
- } catch (Exception e) {
- Slog.e(TAG, "Error in restore thread", e);
- } finally {
- if (DEBUG) Slog.d(TAG, "finishing restore mObserver=" + mObserver);
-
- try {
- mTransport.finishRestore();
- } catch (RemoteException e) {
- Slog.e(TAG, "Error finishing restore", e);
+ if (packageName == null) {
+ Slog.e(TAG, "Error getting next restore package");
+ EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
+ executeNextState(RestoreState.FINAL);
+ return;
+ } else if (packageName.equals("")) {
+ if (DEBUG) Slog.v(TAG, "No next package, finishing restore");
+ int millis = (int) (SystemClock.elapsedRealtime() - mStartRealtime);
+ EventLog.writeEvent(EventLogTags.RESTORE_SUCCESS, mCount, millis);
+ executeNextState(RestoreState.FINAL);
+ return;
}
if (mObserver != null) {
try {
- mObserver.restoreFinished(error);
+ mObserver.onUpdate(mCount, packageName);
} catch (RemoteException e) {
- Slog.d(TAG, "Restore observer died at restoreFinished");
+ Slog.d(TAG, "Restore observer died in onUpdate");
+ mObserver = null;
}
}
- // If this was a restoreAll operation, record that this was our
- // ancestral dataset, as well as the set of apps that are possibly
- // restoreable from the dataset
- if (mTargetPackage == null && pmAgent != null) {
- mAncestralPackages = pmAgent.getRestoredPackages();
- mAncestralToken = mToken;
- writeRestoreTokens();
+ Metadata metaInfo = mPmAgent.getRestoredMetadata(packageName);
+ if (metaInfo == null) {
+ Slog.e(TAG, "Missing metadata for " + packageName);
+ EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
+ "Package metadata missing");
+ executeNextState(RestoreState.RUNNING_QUEUE);
+ return;
}
- // We must under all circumstances tell the Package Manager to
- // proceed with install notifications if it's waiting for us.
- if (mPmToken > 0) {
- if (DEBUG) Slog.v(TAG, "finishing PM token " + mPmToken);
- try {
- mPackageManagerBinder.finishPackageInstall(mPmToken);
- } catch (RemoteException e) { /* can't happen */ }
+ PackageInfo packageInfo;
+ try {
+ int flags = PackageManager.GET_SIGNATURES;
+ packageInfo = mPackageManager.getPackageInfo(packageName, flags);
+ } catch (NameNotFoundException e) {
+ Slog.e(TAG, "Invalid package restoring data", e);
+ EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
+ "Package missing on device");
+ executeNextState(RestoreState.RUNNING_QUEUE);
+ return;
}
- // Furthermore we need to reset the session timeout clock
- mBackupHandler.removeMessages(MSG_RESTORE_TIMEOUT);
- mBackupHandler.sendEmptyMessageDelayed(MSG_RESTORE_TIMEOUT,
- TIMEOUT_RESTORE_INTERVAL);
+ if (metaInfo.versionCode > packageInfo.versionCode) {
+ // Data is from a "newer" version of the app than we have currently
+ // installed. If the app has not declared that it is prepared to
+ // handle this case, we do not attempt the restore.
+ if ((packageInfo.applicationInfo.flags
+ & ApplicationInfo.FLAG_RESTORE_ANY_VERSION) == 0) {
+ String message = "Version " + metaInfo.versionCode
+ + " > installed version " + packageInfo.versionCode;
+ Slog.w(TAG, "Package " + packageName + ": " + message);
+ EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE,
+ packageName, message);
+ executeNextState(RestoreState.RUNNING_QUEUE);
+ return;
+ } else {
+ if (DEBUG) Slog.v(TAG, "Version " + metaInfo.versionCode
+ + " > installed " + packageInfo.versionCode
+ + " but restoreAnyVersion");
+ }
+ }
- // done; we can finally release the wakelock
- mWakelock.release();
+ if (!signaturesMatch(metaInfo.signatures, packageInfo)) {
+ Slog.w(TAG, "Signature mismatch restoring " + packageName);
+ EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
+ "Signature mismatch");
+ executeNextState(RestoreState.RUNNING_QUEUE);
+ return;
+ }
+
+ if (DEBUG) Slog.v(TAG, "Package " + packageName
+ + " restore version [" + metaInfo.versionCode
+ + "] is compatible with installed version ["
+ + packageInfo.versionCode + "]");
+
+ // Then set up and bind the agent
+ IBackupAgent agent = bindToAgentSynchronous(
+ packageInfo.applicationInfo,
+ IApplicationThread.BACKUP_MODE_INCREMENTAL);
+ if (agent == null) {
+ Slog.w(TAG, "Can't find backup agent for " + packageName);
+ EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
+ "Restore agent missing");
+ executeNextState(RestoreState.RUNNING_QUEUE);
+ return;
+ }
+
+ // And then finally start the restore on this agent
+ try {
+ initiateOneRestore(packageInfo, metaInfo.versionCode, agent, mNeedFullBackup);
+ ++mCount;
+ } catch (Exception e) {
+ Slog.e(TAG, "Error when attempting restore: " + e.toString());
+ agentErrorCleanup();
+ executeNextState(RestoreState.RUNNING_QUEUE);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to fetch restore data from transport");
+ mStatus = BackupConstants.TRANSPORT_ERROR;
+ executeNextState(RestoreState.FINAL);
}
}
- // Do the guts of a restore of one application, using mTransport.getRestoreData().
- void processOneRestore(PackageInfo app, int appVersionCode, IBackupAgent agent,
+ void finalizeRestore() {
+ if (MORE_DEBUG) Slog.d(TAG, "finishing restore mObserver=" + mObserver);
+
+ try {
+ mTransport.finishRestore();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error finishing restore", e);
+ }
+
+ if (mObserver != null) {
+ try {
+ mObserver.restoreFinished(mStatus);
+ } catch (RemoteException e) {
+ Slog.d(TAG, "Restore observer died at restoreFinished");
+ }
+ }
+
+ // If this was a restoreAll operation, record that this was our
+ // ancestral dataset, as well as the set of apps that are possibly
+ // restoreable from the dataset
+ if (mTargetPackage == null && mPmAgent != null) {
+ mAncestralPackages = mPmAgent.getRestoredPackages();
+ mAncestralToken = mToken;
+ writeRestoreTokens();
+ }
+
+ // We must under all circumstances tell the Package Manager to
+ // proceed with install notifications if it's waiting for us.
+ if (mPmToken > 0) {
+ if (MORE_DEBUG) Slog.v(TAG, "finishing PM token " + mPmToken);
+ try {
+ mPackageManagerBinder.finishPackageInstall(mPmToken);
+ } catch (RemoteException e) { /* can't happen */ }
+ }
+
+ // Furthermore we need to reset the session timeout clock
+ mBackupHandler.removeMessages(MSG_RESTORE_TIMEOUT);
+ mBackupHandler.sendEmptyMessageDelayed(MSG_RESTORE_TIMEOUT,
+ TIMEOUT_RESTORE_INTERVAL);
+
+ // done; we can finally release the wakelock
+ Slog.i(TAG, "Restore complete.");
+ mWakelock.release();
+ }
+
+ // Call asynchronously into the app, passing it the restore data. The next step
+ // after this is always a callback, either operationComplete() or handleTimeout().
+ void initiateOneRestore(PackageInfo app, int appVersionCode, IBackupAgent agent,
boolean needFullBackup) {
- // !!! TODO: actually run the restore through mTransport
+ mCurrentPackage = app;
final String packageName = app.packageName;
- if (DEBUG) Slog.d(TAG, "processOneRestore packageName=" + packageName);
+ if (DEBUG) Slog.d(TAG, "initiateOneRestore packageName=" + packageName);
// !!! TODO: get the dirs from the transport
- File backupDataName = new File(mDataDir, packageName + ".restore");
- File newStateName = new File(mStateDir, packageName + ".new");
- File savedStateName = new File(mStateDir, packageName);
-
- ParcelFileDescriptor backupData = null;
- ParcelFileDescriptor newState = null;
+ mBackupDataName = new File(mDataDir, packageName + ".restore");
+ mNewStateName = new File(mStateDir, packageName + ".new");
+ mSavedStateName = new File(mStateDir, packageName);
final int token = generateToken();
try {
// Run the transport's restore pass
- backupData = ParcelFileDescriptor.open(backupDataName,
+ mBackupData = ParcelFileDescriptor.open(mBackupDataName,
ParcelFileDescriptor.MODE_READ_WRITE |
ParcelFileDescriptor.MODE_CREATE |
ParcelFileDescriptor.MODE_TRUNCATE);
- if (mTransport.getRestoreData(backupData) != BackupConstants.TRANSPORT_OK) {
+ if (mTransport.getRestoreData(mBackupData) != BackupConstants.TRANSPORT_OK) {
Slog.e(TAG, "Error getting restore data for " + packageName);
EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
return;
}
// Okay, we have the data. Now have the agent do the restore.
- backupData.close();
- backupData = ParcelFileDescriptor.open(backupDataName,
+ mBackupData.close();
+ mBackupData = ParcelFileDescriptor.open(mBackupDataName,
ParcelFileDescriptor.MODE_READ_ONLY);
- newState = ParcelFileDescriptor.open(newStateName,
+ mNewState = ParcelFileDescriptor.open(mNewStateName,
ParcelFileDescriptor.MODE_READ_WRITE |
ParcelFileDescriptor.MODE_CREATE |
ParcelFileDescriptor.MODE_TRUNCATE);
// Kick off the restore, checking for hung agents
- prepareOperationTimeout(token, TIMEOUT_RESTORE_INTERVAL);
- agent.doRestore(backupData, appVersionCode, newState, token, mBackupManagerBinder);
- boolean success = waitUntilOperationComplete(token);
-
- if (!success) {
- throw new RuntimeException("restore timeout");
- }
-
- // if everything went okay, remember the recorded state now
- //
- // !!! TODO: the restored data should be migrated on the server
- // side into the current dataset. In that case the new state file
- // we just created would reflect the data already extant in the
- // backend, so there'd be nothing more to do. Until that happens,
- // however, we need to make sure that we record the data to the
- // current backend dataset. (Yes, this means shipping the data over
- // the wire in both directions. That's bad, but consistency comes
- // first, then efficiency.) Once we introduce server-side data
- // migration to the newly-restored device's dataset, we will change
- // the following from a discard of the newly-written state to the
- // "correct" operation of renaming into the canonical state blob.
- newStateName.delete(); // TODO: remove; see above comment
- //newStateName.renameTo(savedStateName); // TODO: replace with this
-
- int size = (int) backupDataName.length();
- EventLog.writeEvent(EventLogTags.RESTORE_PACKAGE, packageName, size);
+ prepareOperationTimeout(token, TIMEOUT_RESTORE_INTERVAL, this);
+ agent.doRestore(mBackupData, appVersionCode, mNewState, token, mBackupManagerBinder);
} catch (Exception e) {
- Slog.e(TAG, "Error restoring data for " + packageName, e);
+ Slog.e(TAG, "Unable to call app for restore: " + packageName, e);
EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName, e.toString());
+ agentErrorCleanup(); // clears any pending timeout messages as well
- // If the agent fails restore, it might have put the app's data
- // into an incoherent state. For consistency we wipe its data
- // again in this case before propagating the exception
- clearApplicationDataSynchronous(packageName);
- } finally {
- backupDataName.delete();
- try { if (backupData != null) backupData.close(); } catch (IOException e) {}
- try { if (newState != null) newState.close(); } catch (IOException e) {}
- backupData = newState = null;
- synchronized (mCurrentOperations) {
- mCurrentOperations.delete(token);
- }
+ // After a restore failure we go back to running the queue. If there
+ // are no more packages to be restored that will be handled by the
+ // next step.
+ executeNextState(RestoreState.RUNNING_QUEUE);
+ }
+ }
- // If we know a priori that we'll need to perform a full post-restore backup
- // pass, clear the new state file data. This means we're discarding work that
- // was just done by the app's agent, but this way the agent doesn't need to
- // take any special action based on global device state.
- if (needFullBackup) {
- newStateName.delete();
+ void agentErrorCleanup() {
+ // If the agent fails restore, it might have put the app's data
+ // into an incoherent state. For consistency we wipe its data
+ // again in this case before continuing with normal teardown
+ clearApplicationDataSynchronous(mCurrentPackage.packageName);
+ agentCleanup();
+ }
+
+ void agentCleanup() {
+ mBackupDataName.delete();
+ try { if (mBackupData != null) mBackupData.close(); } catch (IOException e) {}
+ try { if (mNewState != null) mNewState.close(); } catch (IOException e) {}
+ mBackupData = mNewState = null;
+
+ // if everything went okay, remember the recorded state now
+ //
+ // !!! TODO: the restored data should be migrated on the server
+ // side into the current dataset. In that case the new state file
+ // we just created would reflect the data already extant in the
+ // backend, so there'd be nothing more to do. Until that happens,
+ // however, we need to make sure that we record the data to the
+ // current backend dataset. (Yes, this means shipping the data over
+ // the wire in both directions. That's bad, but consistency comes
+ // first, then efficiency.) Once we introduce server-side data
+ // migration to the newly-restored device's dataset, we will change
+ // the following from a discard of the newly-written state to the
+ // "correct" operation of renaming into the canonical state blob.
+ mNewStateName.delete(); // TODO: remove; see above comment
+ //mNewStateName.renameTo(mSavedStateName); // TODO: replace with this
+
+ // If this wasn't the PM pseudopackage, tear down the agent side
+ if (mCurrentPackage.applicationInfo != null) {
+ // unbind and tidy up even on timeout or failure
+ try {
+ mActivityManager.unbindBackupAgent(mCurrentPackage.applicationInfo);
+
+ // The agent was probably running with a stub Application object,
+ // which isn't a valid run mode for the main app logic. Shut
+ // down the app so that next time it's launched, it gets the
+ // usual full initialization. Note that this is only done for
+ // full-system restores: when a single app has requested a restore,
+ // it is explicitly not killed following that operation.
+ if (mTargetPackage == null && (mCurrentPackage.applicationInfo.flags
+ & ApplicationInfo.FLAG_KILL_AFTER_RESTORE) != 0) {
+ if (DEBUG) Slog.d(TAG, "Restore complete, killing host process of "
+ + mCurrentPackage.applicationInfo.processName);
+ mActivityManager.killApplicationProcess(
+ mCurrentPackage.applicationInfo.processName,
+ mCurrentPackage.applicationInfo.uid);
+ }
+ } catch (RemoteException e) {
+ // can't happen; we run in the same process as the activity manager
}
}
+
+ // The caller is responsible for reestablishing the state machine; our
+ // responsibility here is to clear the decks for whatever comes next.
+ mBackupHandler.removeMessages(MSG_TIMEOUT, this);
+ synchronized (mCurrentOpLock) {
+ mCurrentOperations.clear();
+ }
+ }
+
+ // A call to agent.doRestore() has been positively acknowledged as complete
+ @Override
+ public void operationComplete() {
+ int size = (int) mBackupDataName.length();
+ EventLog.writeEvent(EventLogTags.RESTORE_PACKAGE, mCurrentPackage.packageName, size);
+ // Just go back to running the restore queue
+ agentCleanup();
+
+ executeNextState(RestoreState.RUNNING_QUEUE);
+ }
+
+ // A call to agent.doRestore() has timed out
+ @Override
+ public void handleTimeout() {
+ Slog.e(TAG, "Timeout restoring application " + mCurrentPackage.packageName);
+ EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE,
+ mCurrentPackage.packageName, "restore timeout");
+ // Handle like an agent that threw on invocation: wipe it and go on to the next
+ agentErrorCleanup();
+ executeNextState(RestoreState.RUNNING_QUEUE);
+ }
+
+ void executeNextState(RestoreState nextState) {
+ if (MORE_DEBUG) Slog.i(TAG, " => executing next step on "
+ + this + " nextState=" + nextState);
+ mCurrentState = nextState;
+ Message msg = mBackupHandler.obtainMessage(MSG_BACKUP_RESTORE_STEP, this);
+ mBackupHandler.sendMessage(msg);
}
}
@@ -4884,12 +5279,23 @@
// Note that a currently-active backup agent has notified us that it has
// completed the given outstanding asynchronous backup/restore operation.
+ @Override
public void opComplete(int token) {
+ if (MORE_DEBUG) Slog.v(TAG, "opComplete: " + Integer.toHexString(token));
+ Operation op = null;
synchronized (mCurrentOpLock) {
- if (MORE_DEBUG) Slog.v(TAG, "opComplete: " + Integer.toHexString(token));
- mCurrentOperations.put(token, OP_ACKNOWLEDGED);
+ op = mCurrentOperations.get(token);
+ if (op != null) {
+ op.state = OP_ACKNOWLEDGED;
+ }
mCurrentOpLock.notifyAll();
}
+
+ // The completion callback, if any, is invoked on the handler
+ if (op != null && op.callback != null) {
+ Message msg = mBackupHandler.obtainMessage(MSG_OP_COMPLETE, op.callback);
+ mBackupHandler.sendMessage(msg);
+ }
}
// ----- Restore session -----
diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java
index 1497511..f774dea 100644
--- a/services/java/com/android/server/NetworkManagementService.java
+++ b/services/java/com/android/server/NetworkManagementService.java
@@ -98,6 +98,7 @@
public static final String LIMIT_GLOBAL_ALERT = "globalAlert";
/** {@link #mStatsXtUid} headers. */
+ private static final String KEY_IDX = "idx";
private static final String KEY_IFACE = "iface";
private static final String KEY_UID = "uid_tag_int";
private static final String KEY_COUNTER_SET = "cnt_set";
@@ -1323,17 +1324,20 @@
// TODO: remove knownLines check once 5087722 verified
final HashSet<String> knownLines = Sets.newHashSet();
+ // TODO: remove lastIdx check once 5270106 verified
+ int lastIdx = 0;
final ArrayList<String> keys = Lists.newArrayList();
final ArrayList<String> values = Lists.newArrayList();
final HashMap<String, String> parsed = Maps.newHashMap();
BufferedReader reader = null;
+ String line = null;
try {
reader = new BufferedReader(new FileReader(mStatsXtUid));
// parse first line as header
- String line = reader.readLine();
+ line = reader.readLine();
splitLine(line, keys);
// parse remaining lines
@@ -1342,32 +1346,35 @@
parseLine(keys, values, parsed);
if (!knownLines.add(line)) {
- throw new IllegalStateException("encountered duplicate proc entry");
+ throw new IllegalStateException("duplicate proc entry: " + line);
}
- try {
- entry.iface = parsed.get(KEY_IFACE);
- entry.uid = getParsedInt(parsed, KEY_UID);
- entry.set = getParsedInt(parsed, KEY_COUNTER_SET);
- entry.tag = kernelToTag(parsed.get(KEY_TAG_HEX));
- entry.rxBytes = getParsedLong(parsed, KEY_RX_BYTES);
- entry.rxPackets = getParsedLong(parsed, KEY_RX_PACKETS);
- entry.txBytes = getParsedLong(parsed, KEY_TX_BYTES);
- entry.txPackets = getParsedLong(parsed, KEY_TX_PACKETS);
+ final int idx = getParsedInt(parsed, KEY_IDX);
+ if (idx > lastIdx + 1) {
+ throw new IllegalStateException(
+ "inconsistent idx=" + idx + " after lastIdx=" + lastIdx);
+ }
+ lastIdx = idx;
- if (limitUid == UID_ALL || limitUid == entry.uid) {
- stats.addValues(entry);
- }
- } catch (NumberFormatException e) {
- Slog.w(TAG, "problem parsing stats row '" + line + "': " + e);
+ entry.iface = parsed.get(KEY_IFACE);
+ entry.uid = getParsedInt(parsed, KEY_UID);
+ entry.set = getParsedInt(parsed, KEY_COUNTER_SET);
+ entry.tag = kernelToTag(parsed.get(KEY_TAG_HEX));
+ entry.rxBytes = getParsedLong(parsed, KEY_RX_BYTES);
+ entry.rxPackets = getParsedLong(parsed, KEY_RX_PACKETS);
+ entry.txBytes = getParsedLong(parsed, KEY_TX_BYTES);
+ entry.txPackets = getParsedLong(parsed, KEY_TX_PACKETS);
+
+ if (limitUid == UID_ALL || limitUid == entry.uid) {
+ stats.addValues(entry);
}
}
} catch (NullPointerException e) {
- throw new IllegalStateException("problem parsing stats: " + e);
+ throw new IllegalStateException("problem parsing line: " + line, e);
} catch (NumberFormatException e) {
- throw new IllegalStateException("problem parsing stats: " + e);
+ throw new IllegalStateException("problem parsing line: " + line, e);
} catch (IOException e) {
- throw new IllegalStateException("problem parsing stats: " + e);
+ throw new IllegalStateException("problem parsing line: " + line, e);
} finally {
IoUtils.closeQuietly(reader);
}
diff --git a/services/java/com/android/server/net/NetworkPolicyManagerService.java b/services/java/com/android/server/net/NetworkPolicyManagerService.java
index 8af90ff..9067fae 100644
--- a/services/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -100,6 +100,7 @@
import android.telephony.TelephonyManager;
import android.text.format.Formatter;
import android.text.format.Time;
+import android.util.Log;
import android.util.NtpTrustedTime;
import android.util.Slog;
import android.util.SparseArray;
@@ -298,18 +299,9 @@
try {
mActivityManager.registerProcessObserver(mProcessObserver);
- } catch (RemoteException e) {
- // ouch, no foregroundActivities updates means some processes may
- // never get network access.
- Slog.e(TAG, "unable to register IProcessObserver", e);
- }
-
- try {
mNetworkManager.registerObserver(mAlertObserver);
} catch (RemoteException e) {
- // ouch, no alert updates means we fall back to
- // ACTION_NETWORK_STATS_UPDATED broadcasts.
- Slog.e(TAG, "unable to register INetworkManagementEventObserver", e);
+ // ignored; both services live in system_server
}
// TODO: traverse existing processes to know foreground state, or have
@@ -462,7 +454,7 @@
// caused alert to trigger.
mNetworkStats.forceUpdate();
} catch (RemoteException e) {
- Slog.w(TAG, "problem updating network stats");
+ // ignored; service lives in system_server
}
updateNetworkEnabledLocked();
@@ -495,9 +487,7 @@
final long start = computeLastCycleBoundary(currentTime, policy);
final long end = currentTime;
-
final long totalBytes = getTotalBytes(policy.template, start, end);
- if (totalBytes == UNKNOWN_BYTES) continue;
if (policy.limitBytes != LIMIT_DISABLED && totalBytes >= policy.limitBytes) {
if (policy.lastSnooze >= start) {
@@ -671,7 +661,7 @@
packageName, tag, 0x0, builder.getNotification(), idReceived);
mActiveNotifs.add(tag);
} catch (RemoteException e) {
- Slog.w(TAG, "problem during enqueueNotification: " + e);
+ // ignored; service lives in system_server
}
}
@@ -705,7 +695,7 @@
0x0, builder.getNotification(), idReceived);
mActiveNotifs.add(tag);
} catch (RemoteException e) {
- Slog.w(TAG, "problem during enqueueNotification: " + e);
+ // ignored; service lives in system_server
}
}
@@ -716,7 +706,7 @@
mNotifManager.cancelNotificationWithTag(
packageName, tag, 0x0);
} catch (RemoteException e) {
- Slog.w(TAG, "problem during enqueueNotification: " + e);
+ // ignored; service lives in system_server
}
}
@@ -758,9 +748,7 @@
final long start = computeLastCycleBoundary(currentTime, policy);
final long end = currentTime;
-
final long totalBytes = getTotalBytes(policy.template, start, end);
- if (totalBytes == UNKNOWN_BYTES) continue;
// disable data connection when over limit and not snoozed
final boolean overLimit = policy.limitBytes != LIMIT_DISABLED
@@ -810,7 +798,7 @@
try {
states = mConnManager.getAllNetworkState();
} catch (RemoteException e) {
- Slog.w(TAG, "problem reading network state");
+ // ignored; service lives in system_server
return;
}
@@ -857,9 +845,7 @@
final long start = computeLastCycleBoundary(currentTime, policy);
final long end = currentTime;
-
final long totalBytes = getTotalBytes(policy.template, start, end);
- if (totalBytes == UNKNOWN_BYTES) continue;
if (LOGD) {
Slog.d(TAG, "applying policy " + policy.toString() + " to ifaces "
@@ -1006,9 +992,9 @@
// missing policy is okay, probably first boot
upgradeLegacyBackgroundData();
} catch (IOException e) {
- Slog.e(TAG, "problem reading network stats", e);
+ Log.wtf(TAG, "problem reading network policy", e);
} catch (XmlPullParserException e) {
- Slog.e(TAG, "problem reading network stats", e);
+ Log.wtf(TAG, "problem reading network policy", e);
} finally {
IoUtils.closeQuietly(fis);
}
@@ -1246,12 +1232,10 @@
final long currentTime = currentTimeMillis(false);
+ // find total bytes used under policy
final long start = computeLastCycleBoundary(currentTime, policy);
final long end = currentTime;
-
- // find total bytes used under policy
final long totalBytes = getTotalBytes(policy.template, start, end);
- if (totalBytes == UNKNOWN_BYTES) return null;
// report soft and hard limits under policy
final long softLimitBytes = policy.warningBytes != WARNING_DISABLED ? policy.warningBytes
@@ -1369,6 +1353,7 @@
try {
mScreenOn = mPowerManager.isScreenOn();
} catch (RemoteException e) {
+ // ignored; service lives in system_server
}
updateRulesForScreenLocked();
}
@@ -1448,7 +1433,7 @@
// adjust stats accounting based on foreground status
mNetworkStats.setUidForeground(uid, uidForeground);
} catch (RemoteException e) {
- Slog.w(TAG, "problem dispatching foreground change");
+ // ignored; service lives in system_server
}
}
@@ -1498,9 +1483,9 @@
try {
mNetworkManager.setInterfaceQuota(iface, quotaBytes);
} catch (IllegalStateException e) {
- Slog.e(TAG, "problem setting interface quota", e);
+ Log.wtf(TAG, "problem setting interface quota", e);
} catch (RemoteException e) {
- Slog.e(TAG, "problem setting interface quota", e);
+ // ignored; service lives in system_server
}
}
@@ -1508,29 +1493,9 @@
try {
mNetworkManager.removeInterfaceQuota(iface);
} catch (IllegalStateException e) {
- Slog.e(TAG, "problem removing interface quota", e);
+ Log.wtf(TAG, "problem removing interface quota", e);
} catch (RemoteException e) {
- Slog.e(TAG, "problem removing interface quota", e);
- }
- }
-
- private void setInterfaceAlert(String iface, long alertBytes) {
- try {
- mNetworkManager.setInterfaceAlert(iface, alertBytes);
- } catch (IllegalStateException e) {
- Slog.e(TAG, "problem setting interface alert", e);
- } catch (RemoteException e) {
- Slog.e(TAG, "problem setting interface alert", e);
- }
- }
-
- private void removeInterfaceAlert(String iface) {
- try {
- mNetworkManager.removeInterfaceAlert(iface);
- } catch (IllegalStateException e) {
- Slog.e(TAG, "problem removing interface alert", e);
- } catch (RemoteException e) {
- Slog.e(TAG, "problem removing interface alert", e);
+ // ignored; service lives in system_server
}
}
@@ -1538,9 +1503,9 @@
try {
mNetworkManager.setUidNetworkRules(uid, rejectOnQuotaInterfaces);
} catch (IllegalStateException e) {
- Slog.e(TAG, "problem setting uid rules", e);
+ Log.wtf(TAG, "problem setting uid rules", e);
} catch (RemoteException e) {
- Slog.e(TAG, "problem setting uid rules", e);
+ // ignored; service lives in system_server
}
}
@@ -1556,7 +1521,7 @@
try {
mConnManager.setPolicyDataEnable(networkType, enabled);
} catch (RemoteException e) {
- Slog.e(TAG, "problem setting network enabled", e);
+ // ignored; service lives in system_server
}
mActiveNetworkEnabled.put(networkType, enabled);
@@ -1569,8 +1534,6 @@
return telephony.getSubscriberId();
}
- private static final long UNKNOWN_BYTES = -1;
-
private long getTotalBytes(NetworkTemplate template, long start, long end) {
try {
final NetworkStats stats = mNetworkStats.getSummaryForNetwork(
@@ -1578,8 +1541,8 @@
final NetworkStats.Entry entry = stats.getValues(0, null);
return entry.rxBytes + entry.txBytes;
} catch (RemoteException e) {
- Slog.w(TAG, "problem reading summary for template " + template);
- return UNKNOWN_BYTES;
+ // ignored; service lives in system_server
+ return 0;
}
}
diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java
index 4d54fd4..af29d85 100644
--- a/services/java/com/android/server/net/NetworkStatsService.java
+++ b/services/java/com/android/server/net/NetworkStatsService.java
@@ -79,6 +79,7 @@
import android.provider.Settings;
import android.telephony.TelephonyManager;
import android.util.EventLog;
+import android.util.Log;
import android.util.NtpTrustedTime;
import android.util.Slog;
import android.util.SparseIntArray;
@@ -128,7 +129,16 @@
private static final int VERSION_UID_WITH_SET = 4;
private static final int MSG_PERFORM_POLL = 0x1;
- private static final int MSG_PERFORM_POLL_DETAILED = 0x2;
+
+ /** Flags to control detail level of poll event. */
+ private static final int FLAG_POLL_NETWORK = 0x1;
+ private static final int FLAG_POLL_UID = 0x2;
+ private static final int FLAG_PERSIST_NETWORK = 0x10;
+ private static final int FLAG_PERSIST_UID = 0x20;
+ private static final int FLAG_FORCE_PERSIST = 0x100;
+
+ private static final int FLAG_POLL_ALL = FLAG_POLL_NETWORK | FLAG_POLL_UID;
+ private static final int FLAG_PERSIST_ALL = FLAG_PERSIST_NETWORK | FLAG_PERSIST_UID;
private final Context mContext;
private final INetworkManagementService mNetworkManager;
@@ -261,9 +271,7 @@
try {
mNetworkManager.registerObserver(mAlertObserver);
} catch (RemoteException e) {
- // ouch, no push updates means we fall back to
- // ACTION_NETWORK_STATS_POLL intervals.
- Slog.e(TAG, "unable to register INetworkManagementEventObserver", e);
+ // ignored; service lives in system_server
}
registerPollAlarmLocked();
@@ -305,7 +313,7 @@
mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, currentRealtime,
mSettings.getPollInterval(), mPollIntent);
} catch (RemoteException e) {
- Slog.w(TAG, "problem registering for poll alarm: " + e);
+ // ignored; service lives in system_server
}
}
@@ -321,7 +329,7 @@
} catch (IllegalStateException e) {
Slog.w(TAG, "problem registering for global alert: " + e);
} catch (RemoteException e) {
- Slog.w(TAG, "problem registering for global alert: " + e);
+ // ignored; service lives in system_server
}
}
@@ -509,7 +517,7 @@
@Override
public void forceUpdate() {
mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
- performPoll(true, false);
+ performPoll(FLAG_POLL_ALL | FLAG_PERSIST_ALL);
}
/**
@@ -538,7 +546,7 @@
public void onReceive(Context context, Intent intent) {
// on background handler thread, and verified UPDATE_DEVICE_STATS
// permission above.
- performPoll(true, false);
+ performPoll(FLAG_POLL_ALL | FLAG_PERSIST_ALL);
// verify that we're watching global alert
registerGlobalAlert();
@@ -585,7 +593,8 @@
if (LIMIT_GLOBAL_ALERT.equals(limitName)) {
// kick off background poll to collect network stats; UID stats
// are handled during normal polling interval.
- mHandler.obtainMessage(MSG_PERFORM_POLL).sendToTarget();
+ final int flags = FLAG_POLL_NETWORK | FLAG_PERSIST_NETWORK;
+ mHandler.obtainMessage(MSG_PERFORM_POLL, flags, 0).sendToTarget();
// re-arm global alert for next update
registerGlobalAlert();
@@ -605,13 +614,17 @@
// take one last stats snapshot before updating iface mapping. this
// isn't perfect, since the kernel may already be counting traffic from
// the updated network.
- performPollLocked(false, false);
+
+ // poll both network and UID stats, but only persist network stats,
+ // since this codepath should stay fast. UID stats will be persisted
+ // during next alarm poll event.
+ performPollLocked(FLAG_POLL_ALL | FLAG_PERSIST_NETWORK);
final NetworkState[] states;
try {
states = mConnManager.getAllNetworkState();
} catch (RemoteException e) {
- Slog.w(TAG, "problem reading network state");
+ // ignored; service lives in system_server
return;
}
@@ -646,15 +659,15 @@
} catch (IllegalStateException e) {
Slog.w(TAG, "problem reading network stats: " + e);
} catch (RemoteException e) {
- Slog.w(TAG, "problem reading network stats: " + e);
+ // ignored; service lives in system_server
}
}
- private void performPoll(boolean detailedPoll, boolean forcePersist) {
+ private void performPoll(int flags) {
synchronized (mStatsLock) {
mWakeLock.acquire();
try {
- performPollLocked(detailedPoll, forcePersist);
+ performPollLocked(flags);
} finally {
mWakeLock.release();
}
@@ -664,14 +677,17 @@
/**
* Periodic poll operation, reading current statistics and recording into
* {@link NetworkStatsHistory}.
- *
- * @param detailedPoll Indicate if detailed UID stats should be collected
- * during this poll operation.
*/
- private void performPollLocked(boolean detailedPoll, boolean forcePersist) {
- if (LOGV) Slog.v(TAG, "performPollLocked()");
+ private void performPollLocked(int flags) {
+ if (LOGV) Slog.v(TAG, "performPollLocked(flags=0x" + Integer.toHexString(flags) + ")");
final long startRealtime = SystemClock.elapsedRealtime();
+ final boolean pollNetwork = (flags & FLAG_POLL_NETWORK) != 0;
+ final boolean pollUid = (flags & FLAG_POLL_UID) != 0;
+ final boolean persistNetwork = (flags & FLAG_PERSIST_NETWORK) != 0;
+ final boolean persistUid = (flags & FLAG_PERSIST_UID) != 0;
+ final boolean forcePersist = (flags & FLAG_FORCE_PERSIST) != 0;
+
// try refreshing time source when stale
if (mTime.getCacheAge() > mSettings.getTimeCacheMaxAge()) {
mTime.forceRefresh();
@@ -680,41 +696,40 @@
// TODO: consider marking "untrusted" times in historical stats
final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis()
: System.currentTimeMillis();
- final long persistThreshold = mSettings.getPersistThreshold();
+ final long threshold = mSettings.getPersistThreshold();
- final NetworkStats networkSnapshot;
- final NetworkStats uidSnapshot;
try {
- networkSnapshot = mNetworkManager.getNetworkStatsSummary();
- uidSnapshot = detailedPoll ? mNetworkManager.getNetworkStatsUidDetail(UID_ALL) : null;
- } catch (IllegalStateException e) {
- Slog.w(TAG, "problem reading network stats: " + e);
- return;
- } catch (RemoteException e) {
- Slog.w(TAG, "problem reading network stats: " + e);
- return;
- }
+ if (pollNetwork) {
+ final NetworkStats networkSnapshot = mNetworkManager.getNetworkStatsSummary();
+ performNetworkPollLocked(networkSnapshot, currentTime);
- performNetworkPollLocked(networkSnapshot, currentTime);
-
- // persist when enough network data has occurred
- final NetworkStats persistNetworkDelta = computeStatsDelta(
- mLastPersistNetworkSnapshot, networkSnapshot, true);
- if (forcePersist || persistNetworkDelta.getTotalBytes() > persistThreshold) {
- writeNetworkStatsLocked();
- mLastPersistNetworkSnapshot = networkSnapshot;
- }
-
- if (detailedPoll) {
- performUidPollLocked(uidSnapshot, currentTime);
-
- // persist when enough network data has occurred
- final NetworkStats persistUidDelta = computeStatsDelta(
- mLastPersistUidSnapshot, uidSnapshot, true);
- if (forcePersist || persistUidDelta.getTotalBytes() > persistThreshold) {
- writeUidStatsLocked();
- mLastPersistUidSnapshot = networkSnapshot;
+ // persist when enough network data has occurred
+ final NetworkStats persistNetworkDelta = computeStatsDelta(
+ mLastPersistNetworkSnapshot, networkSnapshot, true);
+ final boolean pastThreshold = persistNetworkDelta.getTotalBytes() > threshold;
+ if (forcePersist || (persistNetwork && pastThreshold)) {
+ writeNetworkStatsLocked();
+ mLastPersistNetworkSnapshot = networkSnapshot;
+ }
}
+
+ if (pollUid) {
+ final NetworkStats uidSnapshot = mNetworkManager.getNetworkStatsUidDetail(UID_ALL);
+ performUidPollLocked(uidSnapshot, currentTime);
+
+ // persist when enough network data has occurred
+ final NetworkStats persistUidDelta = computeStatsDelta(
+ mLastPersistUidSnapshot, uidSnapshot, true);
+ final boolean pastThreshold = persistUidDelta.getTotalBytes() > threshold;
+ if (forcePersist || (persistUid && pastThreshold)) {
+ writeUidStatsLocked();
+ mLastPersistUidSnapshot = uidSnapshot;
+ }
+ }
+ } catch (IllegalStateException e) {
+ Log.wtf(TAG, "problem reading network stats", e);
+ } catch (RemoteException e) {
+ // ignored; service lives in system_server
}
if (LOGV) {
@@ -722,8 +737,8 @@
Slog.v(TAG, "performPollLocked() took " + duration + "ms");
}
- // sample stats after detailed poll
- if (detailedPoll) {
+ // sample stats after each full poll
+ if (pollNetwork && pollUid) {
performSample();
}
@@ -785,6 +800,10 @@
entry = delta.getValues(i, entry);
final NetworkIdentitySet ident = mActiveIfaces.get(entry.iface);
if (ident == null) {
+ if (entry.rxBytes > 0 || entry.rxPackets > 0 || entry.txBytes > 0
+ || entry.txPackets > 0) {
+ Log.w(TAG, "dropping UID delta from unknown iface: " + entry);
+ }
continue;
}
@@ -959,7 +978,7 @@
} catch (FileNotFoundException e) {
// missing stats is okay, probably first boot
} catch (IOException e) {
- Slog.e(TAG, "problem reading network stats", e);
+ Log.wtf(TAG, "problem reading network stats", e);
} finally {
IoUtils.closeQuietly(in);
}
@@ -1032,7 +1051,7 @@
} catch (FileNotFoundException e) {
// missing stats is okay, probably first boot
} catch (IOException e) {
- Slog.e(TAG, "problem reading uid stats", e);
+ Log.wtf(TAG, "problem reading uid stats", e);
} finally {
IoUtils.closeQuietly(in);
}
@@ -1061,7 +1080,7 @@
out.flush();
mNetworkFile.finishWrite(fos);
} catch (IOException e) {
- Slog.w(TAG, "problem writing stats: ", e);
+ Log.wtf(TAG, "problem writing stats", e);
if (fos != null) {
mNetworkFile.failWrite(fos);
}
@@ -1115,7 +1134,7 @@
out.flush();
mUidFile.finishWrite(fos);
} catch (IOException e) {
- Slog.w(TAG, "problem writing stats: ", e);
+ Log.wtf(TAG, "problem writing stats", e);
if (fos != null) {
mUidFile.failWrite(fos);
}
@@ -1142,7 +1161,7 @@
}
if (argSet.contains("poll")) {
- performPollLocked(true, true);
+ performPollLocked(FLAG_POLL_ALL | FLAG_PERSIST_ALL | FLAG_FORCE_PERSIST);
pw.println("Forced poll");
return;
}
@@ -1273,11 +1292,8 @@
public boolean handleMessage(Message msg) {
switch (msg.what) {
case MSG_PERFORM_POLL: {
- performPoll(false, false);
- return true;
- }
- case MSG_PERFORM_POLL_DETAILED: {
- performPoll(true, false);
+ final int flags = msg.arg1;
+ performPoll(flags);
return true;
}
default: {
@@ -1349,7 +1365,7 @@
return getSecureLong(NETSTATS_POLL_INTERVAL, 30 * MINUTE_IN_MILLIS);
}
public long getPersistThreshold() {
- return getSecureLong(NETSTATS_PERSIST_THRESHOLD, 512 * KB_IN_BYTES);
+ return getSecureLong(NETSTATS_PERSIST_THRESHOLD, 2 * MB_IN_BYTES);
}
public long getNetworkBucketDuration() {
return getSecureLong(NETSTATS_NETWORK_BUCKET_DURATION, HOUR_IN_MILLIS);
diff --git a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
index 54f3bb0..e7f1d9a 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
@@ -178,6 +178,7 @@
expectDefaultSettings();
expectNetworkState(buildWifiState());
expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
replay();
mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
@@ -230,6 +231,7 @@
expectDefaultSettings();
expectNetworkState(buildWifiState());
expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
replay();
mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
@@ -320,6 +322,7 @@
expectSettings(0L, HOUR_IN_MILLIS, WEEK_IN_MILLIS);
expectNetworkState(buildWifiState());
expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
replay();
mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
@@ -370,6 +373,7 @@
expectDefaultSettings();
expectNetworkState(buildMobile3gState(IMSI_1));
expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
replay();
mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
@@ -450,6 +454,7 @@
expectDefaultSettings();
expectNetworkState(buildWifiState());
expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
replay();
mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
@@ -507,6 +512,7 @@
expectDefaultSettings();
expectNetworkState(buildMobile3gState(IMSI_1));
expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
replay();
mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
@@ -573,6 +579,7 @@
expectDefaultSettings();
expectNetworkState(buildWifiState());
expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
replay();
mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
@@ -635,6 +642,7 @@
expectDefaultSettings();
expectNetworkState(buildWifiState());
expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
replay();
mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
diff --git a/test-runner/src/android/test/InstrumentationCoreTestRunner.java b/test-runner/src/android/test/InstrumentationCoreTestRunner.java
index ff99a74..036a2275 100644
--- a/test-runner/src/android/test/InstrumentationCoreTestRunner.java
+++ b/test-runner/src/android/test/InstrumentationCoreTestRunner.java
@@ -51,35 +51,33 @@
/**
* Convenience definition of our log tag.
- */
+ */
private static final String TAG = "InstrumentationCoreTestRunner";
-
+
/**
* True if (and only if) we are running in single-test mode (as opposed to
* batch mode).
*/
private boolean singleTest = false;
-
+
@Override
public void onCreate(Bundle arguments) {
// We might want to move this to /sdcard, if is is mounted/writable.
File cacheDir = getTargetContext().getCacheDir();
- // Set some properties that the core tests absolutely need.
+ // Set some properties that the core tests absolutely need.
System.setProperty("user.language", "en");
System.setProperty("user.region", "US");
-
+
System.setProperty("java.home", cacheDir.getAbsolutePath());
System.setProperty("user.home", cacheDir.getAbsolutePath());
System.setProperty("java.io.tmpdir", cacheDir.getAbsolutePath());
- System.setProperty("javax.net.ssl.trustStore",
- "/etc/security/cacerts.bks");
-
+
if (arguments != null) {
String classArg = arguments.getString(ARGUMENT_TEST_CLASS);
- singleTest = classArg != null && classArg.contains("#");
+ singleTest = classArg != null && classArg.contains("#");
}
-
+
super.onCreate(arguments);
}
@@ -89,36 +87,36 @@
runner.addTestListener(new TestListener() {
/**
- * The last test class we executed code from.
+ * The last test class we executed code from.
*/
private Class<?> lastClass;
-
+
/**
* The minimum time we expect a test to take.
*/
private static final int MINIMUM_TIME = 100;
-
+
/**
* The start time of our current test in System.currentTimeMillis().
*/
private long startTime;
-
+
public void startTest(Test test) {
if (test.getClass() != lastClass) {
lastClass = test.getClass();
printMemory(test.getClass());
}
-
+
Thread.currentThread().setContextClassLoader(
test.getClass().getClassLoader());
-
+
startTime = System.currentTimeMillis();
}
-
+
public void endTest(Test test) {
if (test instanceof TestCase) {
cleanup((TestCase)test);
-
+
/*
* Make sure all tests take at least MINIMUM_TIME to
* complete. If they don't, we wait a bit. The Cupcake
@@ -126,7 +124,7 @@
* short time, which causes headache for the CTS.
*/
long timeTaken = System.currentTimeMillis() - startTime;
-
+
if (timeTaken < MINIMUM_TIME) {
try {
Thread.sleep(MINIMUM_TIME - timeTaken);
@@ -136,15 +134,15 @@
}
}
}
-
+
public void addError(Test test, Throwable t) {
// This space intentionally left blank.
}
-
+
public void addFailure(Test test, AssertionFailedError t) {
// This space intentionally left blank.
}
-
+
/**
* Dumps some memory info.
*/
@@ -154,7 +152,7 @@
long total = runtime.totalMemory();
long free = runtime.freeMemory();
long used = total - free;
-
+
Log.d(TAG, "Total memory : " + total);
Log.d(TAG, "Used memory : " + used);
Log.d(TAG, "Free memory : " + free);
@@ -170,7 +168,7 @@
*/
private void cleanup(TestCase test) {
Class<?> clazz = test.getClass();
-
+
while (clazz != TestCase.class) {
Field[] fields = clazz.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
@@ -185,15 +183,15 @@
}
}
}
-
+
clazz = clazz.getSuperclass();
}
}
-
+
});
-
+
return runner;
- }
+ }
@Override
List<Predicate<TestMethod>> getBuilderRequirements() {
diff --git a/wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl b/wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl
index a0c7dd1..381a450 100644
--- a/wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl
+++ b/wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl
@@ -26,6 +26,5 @@
interface IWifiP2pManager
{
Messenger getMessenger();
- boolean isP2pSupported();
}
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
index 10a316e..5715186 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
@@ -400,15 +400,6 @@
}
}
- /** @hide */
- public boolean isP2pSupported() {
- try {
- return mService.isP2pSupported();
- } catch (RemoteException e) {
- return false;
- }
- }
-
/**
* Sends in a request to the system to enable p2p. This will pop up a dialog
* to the user and upon authorization will enable p2p.
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pService.java b/wifi/java/android/net/wifi/p2p/WifiP2pService.java
index 361cac5..e2b2249 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pService.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pService.java
@@ -155,8 +155,9 @@
mInterface = SystemProperties.get("wifi.interface", "wlan0");
mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI_P2P, 0, NETWORKTYPE, "");
- mP2pSupported = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_wifi_p2p_support);
+ mP2pSupported = mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_WIFI_DIRECT);
+
mDeviceType = mContext.getResources().getString(
com.android.internal.R.string.config_wifi_p2p_device_type);
mDeviceName = getDefaultDeviceName();
@@ -218,14 +219,6 @@
return new Messenger(mP2pStateMachine.getHandler());
}
- /**
- * Return if p2p is supported
- */
- public boolean isP2pSupported() {
- enforceAccessPermission();
- return mP2pSupported;
- }
-
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
@@ -1218,6 +1211,11 @@
mReplyChannel.replyToMessage(msg, what);
}
+ private void replyToMessage(Message msg, int what, int arg1) {
+ if (msg.replyTo == null) return;
+ mReplyChannel.replyToMessage(msg, what, arg1);
+ }
+
private void replyToMessage(Message msg, int what, Object obj) {
if (msg.replyTo == null) return;
mReplyChannel.replyToMessage(msg, what, obj);