summaryrefslogtreecommitdiffstats
path: root/core/java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/animation/AnimatorInflater.java164
-rw-r--r--core/java/android/app/ActivityTransitionCoordinator.java7
-rw-r--r--core/java/android/app/ApplicationThreadNative.java1
-rw-r--r--core/java/android/app/backup/BackupTransport.java33
-rw-r--r--core/java/android/content/pm/ActivityInfo.java7
-rw-r--r--core/java/android/content/pm/PackageManager.java1
-rw-r--r--core/java/android/content/pm/PackageParser.java8
-rw-r--r--core/java/android/hardware/Camera.java131
-rw-r--r--core/java/android/hardware/ICameraService.aidl7
-rw-r--r--core/java/android/hardware/camera2/CameraMetadata.java4
-rw-r--r--core/java/android/hardware/camera2/CaptureRequest.java3
-rw-r--r--core/java/android/hardware/camera2/impl/CameraDeviceImpl.java276
-rw-r--r--core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java2
-rw-r--r--core/java/android/hardware/camera2/legacy/GLThreadManager.java1
-rw-r--r--core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java77
-rw-r--r--core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java189
-rw-r--r--core/java/android/hardware/camera2/legacy/RequestThreadManager.java101
-rw-r--r--core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java1
-rw-r--r--core/java/android/hardware/camera2/params/StreamConfigurationMap.java16
-rw-r--r--core/java/android/hardware/camera2/utils/CloseableLock.java330
-rw-r--r--core/java/android/nfc/INfcAdapter.aidl3
-rw-r--r--core/java/android/nfc/INfcLockscreenDispatch.aidl12
-rw-r--r--core/java/android/nfc/NfcAdapter.java27
-rw-r--r--core/java/android/provider/ContactsContract.java21
-rw-r--r--core/java/android/provider/Settings.java6
-rw-r--r--core/java/android/util/PathParser.java528
-rw-r--r--core/java/android/view/GLES20Canvas.java26
-rw-r--r--core/java/android/view/View.java1
-rw-r--r--core/java/android/view/ViewGroup.java6
-rw-r--r--core/java/android/view/animation/PathInterpolator.java52
-rw-r--r--core/java/android/widget/GridView.java26
-rw-r--r--core/java/android/widget/ListView.java18
-rw-r--r--core/java/android/widget/RemoteViews.java4
-rw-r--r--core/java/android/widget/Switch.java46
-rw-r--r--core/java/com/android/internal/app/ProcessStats.java499
-rw-r--r--core/java/com/android/internal/app/WindowDecorActionBar.java4
-rw-r--r--core/java/com/android/internal/backup/LocalTransport.java171
-rw-r--r--core/java/com/android/internal/os/BatteryStatsImpl.java9
38 files changed, 2432 insertions, 386 deletions
diff --git a/core/java/android/animation/AnimatorInflater.java b/core/java/android/animation/AnimatorInflater.java
index 06f5aca..39fcf73 100644
--- a/core/java/android/animation/AnimatorInflater.java
+++ b/core/java/android/animation/AnimatorInflater.java
@@ -17,11 +17,13 @@ package android.animation;
import android.content.Context;
import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
-import android.content.res.Resources.NotFoundException;
+import android.graphics.Path;
import android.util.AttributeSet;
+import android.util.PathParser;
import android.util.StateSet;
import android.util.TypedValue;
import android.util.Xml;
@@ -158,7 +160,7 @@ public class AnimatorInflater {
int stateIndex = 0;
for (int i = 0; i < attributeCount; i++) {
int attrName = attributeSet.getAttributeNameResource(i);
- if (attrName == com.android.internal.R.attr.animation) {
+ if (attrName == R.attr.animation) {
animator = loadAnimator(context,
attributeSet.getAttributeResourceValue(i, 0));
} else {
@@ -186,36 +188,43 @@ public class AnimatorInflater {
}
}
+ /**
+ * @param anim Null if this is a ValueAnimator, otherwise this is an
+ * ObjectAnimator
+ * @param arrayAnimator Incoming typed array for Animator's attributes.
+ * @param arrayObjectAnimator Incoming typed array for Object Animator's
+ * attributes.
+ */
+ private static void parseAnimatorFromTypeArray(ValueAnimator anim,
+ TypedArray arrayAnimator, TypedArray arrayObjectAnimator) {
+ long duration = arrayAnimator.getInt(R.styleable.Animator_duration, 300);
- private static void parseAnimatorFromTypeArray(ValueAnimator anim, TypedArray a) {
- long duration = a.getInt(com.android.internal.R.styleable.Animator_duration, 300);
-
- long startDelay = a.getInt(com.android.internal.R.styleable.Animator_startOffset, 0);
+ long startDelay = arrayAnimator.getInt(R.styleable.Animator_startOffset, 0);
- int valueType = a.getInt(com.android.internal.R.styleable.Animator_valueType,
+ int valueType = arrayAnimator.getInt(R.styleable.Animator_valueType,
VALUE_TYPE_FLOAT);
if (anim == null) {
anim = new ValueAnimator();
}
- TypeEvaluator evaluator = null;
- int valueFromIndex = com.android.internal.R.styleable.Animator_valueFrom;
- int valueToIndex = com.android.internal.R.styleable.Animator_valueTo;
+ TypeEvaluator evaluator = null;
+ int valueFromIndex = R.styleable.Animator_valueFrom;
+ int valueToIndex = R.styleable.Animator_valueTo;
boolean getFloats = (valueType == VALUE_TYPE_FLOAT);
- TypedValue tvFrom = a.peekValue(valueFromIndex);
+ TypedValue tvFrom = arrayAnimator.peekValue(valueFromIndex);
boolean hasFrom = (tvFrom != null);
int fromType = hasFrom ? tvFrom.type : 0;
- TypedValue tvTo = a.peekValue(valueToIndex);
+ TypedValue tvTo = arrayAnimator.peekValue(valueToIndex);
boolean hasTo = (tvTo != null);
int toType = hasTo ? tvTo.type : 0;
if ((hasFrom && (fromType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
(fromType <= TypedValue.TYPE_LAST_COLOR_INT)) ||
- (hasTo && (toType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
- (toType <= TypedValue.TYPE_LAST_COLOR_INT))) {
+ (hasTo && (toType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
+ (toType <= TypedValue.TYPE_LAST_COLOR_INT))) {
// special case for colors: ignore valueType and get ints
getFloats = false;
evaluator = ArgbEvaluator.getInstance();
@@ -226,15 +235,15 @@ public class AnimatorInflater {
float valueTo;
if (hasFrom) {
if (fromType == TypedValue.TYPE_DIMENSION) {
- valueFrom = a.getDimension(valueFromIndex, 0f);
+ valueFrom = arrayAnimator.getDimension(valueFromIndex, 0f);
} else {
- valueFrom = a.getFloat(valueFromIndex, 0f);
+ valueFrom = arrayAnimator.getFloat(valueFromIndex, 0f);
}
if (hasTo) {
if (toType == TypedValue.TYPE_DIMENSION) {
- valueTo = a.getDimension(valueToIndex, 0f);
+ valueTo = arrayAnimator.getDimension(valueToIndex, 0f);
} else {
- valueTo = a.getFloat(valueToIndex, 0f);
+ valueTo = arrayAnimator.getFloat(valueToIndex, 0f);
}
anim.setFloatValues(valueFrom, valueTo);
} else {
@@ -242,9 +251,9 @@ public class AnimatorInflater {
}
} else {
if (toType == TypedValue.TYPE_DIMENSION) {
- valueTo = a.getDimension(valueToIndex, 0f);
+ valueTo = arrayAnimator.getDimension(valueToIndex, 0f);
} else {
- valueTo = a.getFloat(valueToIndex, 0f);
+ valueTo = arrayAnimator.getFloat(valueToIndex, 0f);
}
anim.setFloatValues(valueTo);
}
@@ -253,21 +262,21 @@ public class AnimatorInflater {
int valueTo;
if (hasFrom) {
if (fromType == TypedValue.TYPE_DIMENSION) {
- valueFrom = (int) a.getDimension(valueFromIndex, 0f);
+ valueFrom = (int) arrayAnimator.getDimension(valueFromIndex, 0f);
} else if ((fromType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
(fromType <= TypedValue.TYPE_LAST_COLOR_INT)) {
- valueFrom = a.getColor(valueFromIndex, 0);
+ valueFrom = arrayAnimator.getColor(valueFromIndex, 0);
} else {
- valueFrom = a.getInt(valueFromIndex, 0);
+ valueFrom = arrayAnimator.getInt(valueFromIndex, 0);
}
if (hasTo) {
if (toType == TypedValue.TYPE_DIMENSION) {
- valueTo = (int) a.getDimension(valueToIndex, 0f);
+ valueTo = (int) arrayAnimator.getDimension(valueToIndex, 0f);
} else if ((toType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
(toType <= TypedValue.TYPE_LAST_COLOR_INT)) {
- valueTo = a.getColor(valueToIndex, 0);
+ valueTo = arrayAnimator.getColor(valueToIndex, 0);
} else {
- valueTo = a.getInt(valueToIndex, 0);
+ valueTo = arrayAnimator.getInt(valueToIndex, 0);
}
anim.setIntValues(valueFrom, valueTo);
} else {
@@ -276,12 +285,12 @@ public class AnimatorInflater {
} else {
if (hasTo) {
if (toType == TypedValue.TYPE_DIMENSION) {
- valueTo = (int) a.getDimension(valueToIndex, 0f);
+ valueTo = (int) arrayAnimator.getDimension(valueToIndex, 0f);
} else if ((toType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
- (toType <= TypedValue.TYPE_LAST_COLOR_INT)) {
- valueTo = a.getColor(valueToIndex, 0);
+ (toType <= TypedValue.TYPE_LAST_COLOR_INT)) {
+ valueTo = arrayAnimator.getColor(valueToIndex, 0);
} else {
- valueTo = a.getInt(valueToIndex, 0);
+ valueTo = arrayAnimator.getInt(valueToIndex, 0);
}
anim.setIntValues(valueTo);
}
@@ -291,18 +300,59 @@ public class AnimatorInflater {
anim.setDuration(duration);
anim.setStartDelay(startDelay);
- if (a.hasValue(com.android.internal.R.styleable.Animator_repeatCount)) {
+ if (arrayAnimator.hasValue(R.styleable.Animator_repeatCount)) {
anim.setRepeatCount(
- a.getInt(com.android.internal.R.styleable.Animator_repeatCount, 0));
+ arrayAnimator.getInt(R.styleable.Animator_repeatCount, 0));
}
- if (a.hasValue(com.android.internal.R.styleable.Animator_repeatMode)) {
+ if (arrayAnimator.hasValue(R.styleable.Animator_repeatMode)) {
anim.setRepeatMode(
- a.getInt(com.android.internal.R.styleable.Animator_repeatMode,
+ arrayAnimator.getInt(R.styleable.Animator_repeatMode,
ValueAnimator.RESTART));
}
if (evaluator != null) {
anim.setEvaluator(evaluator);
}
+
+ if (arrayObjectAnimator != null) {
+ ObjectAnimator oa = (ObjectAnimator) anim;
+ String pathData = arrayObjectAnimator.getString(R.styleable.PropertyAnimator_pathData);
+
+ // Note that if there is a pathData defined in the Object Animator,
+ // valueFrom / valueTo will be overwritten by the pathData.
+ if (pathData != null) {
+ String propertyXName =
+ arrayObjectAnimator.getString(R.styleable.PropertyAnimator_propertyXName);
+ String propertyYName =
+ arrayObjectAnimator.getString(R.styleable.PropertyAnimator_propertyYName);
+
+ if (propertyXName == null && propertyYName == null) {
+ throw new IllegalArgumentException("propertyXName or propertyYName"
+ + " is needed for PathData in Object Animator");
+ } else {
+ Path path = PathParser.createPathFromPathData(pathData);
+ Keyframe[][] keyframes = PropertyValuesHolder.createKeyframes(path, !getFloats);
+ PropertyValuesHolder x = null;
+ PropertyValuesHolder y = null;
+ if (propertyXName != null) {
+ x = PropertyValuesHolder.ofKeyframe(propertyXName, keyframes[0]);
+ }
+ if (propertyYName != null) {
+ y = PropertyValuesHolder.ofKeyframe(propertyYName, keyframes[1]);
+ }
+ if (x == null) {
+ oa.setValues(y);
+ } else if (y == null) {
+ oa.setValues(x);
+ } else {
+ oa.setValues(x, y);
+ }
+ }
+ } else {
+ String propertyName =
+ arrayObjectAnimator.getString(R.styleable.PropertyAnimator_propertyName);
+ oa.setPropertyName(propertyName);
+ }
+ }
}
private static Animator createAnimatorFromXml(Resources res, Theme theme, XmlPullParser parser)
@@ -338,11 +388,11 @@ public class AnimatorInflater {
anim = new AnimatorSet();
TypedArray a;
if (theme != null) {
- a = theme.obtainStyledAttributes(attrs, com.android.internal.R.styleable.AnimatorSet, 0, 0);
+ a = theme.obtainStyledAttributes(attrs, R.styleable.AnimatorSet, 0, 0);
} else {
- a = res.obtainAttributes(attrs, com.android.internal.R.styleable.AnimatorSet);
+ a = res.obtainAttributes(attrs, R.styleable.AnimatorSet);
}
- int ordering = a.getInt(com.android.internal.R.styleable.AnimatorSet_ordering,
+ int ordering = a.getInt(R.styleable.AnimatorSet_ordering,
TOGETHER);
createAnimatorFromXml(res, theme, parser, attrs, (AnimatorSet) anim, ordering);
a.recycle();
@@ -380,19 +430,6 @@ public class AnimatorInflater {
loadAnimator(res, theme, attrs, anim);
- TypedArray a;
- if (theme != null) {
- a = theme.obtainStyledAttributes(attrs, R.styleable.PropertyAnimator, 0, 0);
- } else {
- a = res.obtainAttributes(attrs, R.styleable.PropertyAnimator);
- }
-
- String propertyName = a.getString(R.styleable.PropertyAnimator_propertyName);
-
- anim.setPropertyName(propertyName);
-
- a.recycle();
-
return anim;
}
@@ -402,26 +439,41 @@ public class AnimatorInflater {
*
* @param res The resources
* @param attrs The set of attributes holding the animation parameters
+ * @param anim Null if this is a ValueAnimator, otherwise this is an
+ * ObjectAnimator
*/
private static ValueAnimator loadAnimator(Resources res, Theme theme,
AttributeSet attrs, ValueAnimator anim)
throws NotFoundException {
- TypedArray a;
+ TypedArray arrayAnimator = null;
+ TypedArray arrayObjectAnimator = null;
+
if (theme != null) {
- a = theme.obtainStyledAttributes(attrs, R.styleable.Animator, 0, 0);
+ arrayAnimator = theme.obtainStyledAttributes(attrs, R.styleable.Animator, 0, 0);
} else {
- a = res.obtainAttributes(attrs, R.styleable.Animator);
+ arrayAnimator = res.obtainAttributes(attrs, R.styleable.Animator);
}
- parseAnimatorFromTypeArray(anim, a);
+ // If anim is not null, then it is an object animator.
+ if (anim != null) {
+ if (theme != null) {
+ arrayObjectAnimator = theme.obtainStyledAttributes(attrs,
+ R.styleable.PropertyAnimator, 0, 0);
+ } else {
+ arrayObjectAnimator = res.obtainAttributes(attrs, R.styleable.PropertyAnimator);
+ }
+ }
+ parseAnimatorFromTypeArray(anim, arrayAnimator, arrayObjectAnimator);
final int resID =
- a.getResourceId(com.android.internal.R.styleable.Animator_interpolator, 0);
+ arrayAnimator.getResourceId(R.styleable.Animator_interpolator, 0);
if (resID > 0) {
anim.setInterpolator(AnimationUtils.loadInterpolator(res, theme, resID));
}
- a.recycle();
+
+ arrayAnimator.recycle();
+ arrayObjectAnimator.recycle();
return anim;
}
diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java
index c7030b0..fa9b1e3 100644
--- a/core/java/android/app/ActivityTransitionCoordinator.java
+++ b/core/java/android/app/ActivityTransitionCoordinator.java
@@ -233,6 +233,13 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver {
if (getViewsTransition() != null) {
getDecor().captureTransitioningViews(mTransitioningViews);
mTransitioningViews.removeAll(mSharedElements);
+ Rect r = new Rect();
+ for (int i = mTransitioningViews.size() - 1; i >= 0; i--) {
+ View view = mTransitioningViews.get(i);
+ if (!view.getGlobalVisibleRect(r)) {
+ mTransitioningViews.remove(i);
+ }
+ }
}
setEpicenter();
}
diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java
index ef4099f..5998d7a 100644
--- a/core/java/android/app/ApplicationThreadNative.java
+++ b/core/java/android/app/ApplicationThreadNative.java
@@ -1184,6 +1184,7 @@ class ApplicationThreadProxy implements IApplicationThread {
data.writeInt(level);
mRemote.transact(SCHEDULE_TRIM_MEMORY_TRANSACTION, data, null,
IBinder.FLAG_ONEWAY);
+ data.recycle();
}
public void dumpMemInfo(FileDescriptor fd, Debug.MemoryInfo mem, boolean checkin,
diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java
index 706ef04..4631323 100644
--- a/core/java/android/app/backup/BackupTransport.java
+++ b/core/java/android/app/backup/BackupTransport.java
@@ -31,12 +31,22 @@ import com.android.internal.backup.IBackupTransport;
* @hide
*/
public class BackupTransport {
+ // Zero return always means things are okay. If returned from
+ // getNextFullRestoreDataChunk(), it means that no data could be delivered at
+ // this time, but the restore is still running and the caller should simply
+ // retry.
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 TRANSPORT_PACKAGE_REJECTED = 3;
- public static final int AGENT_ERROR = 4;
- public static final int AGENT_UNKNOWN = 5;
+
+ // -1 is special; it is used in getNextFullRestoreDataChunk() to indicate that
+ // we've delivered the entire data stream for the current restore target.
+ public static final int NO_MORE_DATA = -1;
+
+ // Result codes that indicate real errors are negative and not -1
+ public static final int TRANSPORT_ERROR = -1000;
+ public static final int TRANSPORT_NOT_INITIALIZED = -1001;
+ public static final int TRANSPORT_PACKAGE_REJECTED = -1002;
+ public static final int AGENT_ERROR = -1003;
+ public static final int AGENT_UNKNOWN = -1004;
IBackupTransport mBinderImpl = new TransportImpl();
/** @hide */
@@ -370,11 +380,14 @@ public class BackupTransport {
* @param socket The file descriptor that the transport will use for delivering the
* streamed archive. The transport must close this socket in all cases when returning
* from this method.
- * @return 0 when no more data for the current package is available. A positive value
- * indicates the presence of that many bytes to be delivered to the app. Any negative
- * return value is treated as equivalent to {@link BackupTransport#TRANSPORT_ERROR},
- * indicating a fatal error condition that precludes further restore operations
- * on the current dataset.
+ * @return {@link #NO_MORE_DATA} when no more data for the current package is available.
+ * A positive value indicates the presence of that many bytes to be delivered to the app.
+ * A value of zero indicates that no data was deliverable at this time, but the restore
+ * is still running and the caller should retry. {@link #TRANSPORT_PACKAGE_REJECTED}
+ * means that the current package's restore operation should be aborted, but that
+ * the transport itself is still in a good state and so a multiple-package restore
+ * sequence can still be continued. Any other negative return value is treated as a
+ * fatal error condition that aborts all further restore operations on the current dataset.
*/
public int getNextFullRestoreDataChunk(ParcelFileDescriptor socket) {
return 0;
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index abc8cde..bcf1e87 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -252,6 +252,13 @@ public class ActivityInfo extends ComponentInfo
*/
public static final int FLAG_IMMERSIVE = 0x0800;
/**
+ * Bit in {@link #flags}: If set, a task rooted at this activity will have its
+ * baseIntent replaced by the activity immediately above this. Each activity may further
+ * relinquish its identity to the activity above it using this flag. Set from the
+ * android.R.attr#relinquishTaskIdentity attribute.
+ */
+ public static final int FLAG_RELINQUISH_TASK_IDENTITY = 0x1000;
+ /**
* Bit in {@link #flags} indicating that tasks started with this activity are to be
* removed from the recent list of tasks when the last activity in the task is finished.
* {@link android.R.attr#autoRemoveFromRecents}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index b5ceebe..9d871c5 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2892,6 +2892,7 @@ public abstract class PackageManager {
PackageParser.Package pkg = parser.parseMonolithicPackage(apkFile, 0);
if ((flags & GET_SIGNATURES) != 0) {
parser.collectCertificates(pkg, 0);
+ parser.collectManifestDigest(pkg);
}
PackageUserState state = new PackageUserState();
return PackageParser.generatePackageInfo(pkg, null, flags, 0, 0, null, state);
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 546f3a5..b40a441 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -744,6 +744,8 @@ public class PackageParser {
* {@code AndroidManifest.xml}, {@code true} is returned.
*/
public void collectManifestDigest(Package pkg) throws PackageParserException {
+ pkg.manifestDigest = null;
+
// TODO: extend to gather digest for split APKs
try {
final StrictJarFile jarFile = new StrictJarFile(pkg.codePath);
@@ -2627,6 +2629,12 @@ public class PackageParser {
false)) {
a.info.flags |= ActivityInfo.FLAG_AUTO_REMOVE_FROM_RECENTS;
}
+
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestActivity_relinquishTaskIdentity,
+ false)) {
+ a.info.flags |= ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY;
+ }
} else {
a.info.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
a.info.configChanges = 0;
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 0705e0c..44fc3b6 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -172,6 +172,11 @@ public class Camera {
private static final int NO_ERROR = 0;
private static final int EACCESS = -13;
private static final int ENODEV = -19;
+ private static final int EBUSY = -16;
+ private static final int EINVAL = -22;
+ private static final int ENOSYS = -38;
+ private static final int EUSERS = -87;
+ private static final int EOPNOTSUPP = -95;
/**
* Broadcast Action: A new picture is taken by the camera, and the entry of
@@ -190,6 +195,22 @@ public class Camera {
public static final String ACTION_NEW_VIDEO = "android.hardware.action.NEW_VIDEO";
/**
+ * Camera HAL device API version 1.0
+ * @hide
+ */
+ public static final int CAMERA_HAL_API_VERSION_1_0 = 0x100;
+
+ /**
+ * A constant meaning the normal camera connect/open will be used.
+ * @hide
+ */
+ public static final int CAMERA_HAL_API_VERSION_NORMAL_OPEN = -2;
+
+ /**
+ * Used to indicate HAL version un-specified.
+ */
+ private static final int CAMERA_HAL_API_VERSION_UNSPECIFIED = -1;
+ /**
* Hardware face detection. It does not use much CPU.
*/
private static final int CAMERA_FACE_DETECTION_HW = 0;
@@ -331,6 +352,111 @@ public class Camera {
return null;
}
+ /**
+ * Creates a new Camera object to access a particular hardware camera with
+ * given hal API version. If the same camera is opened by other applications
+ * or the hal API version is not supported by this device, this will throw a
+ * RuntimeException.
+ * <p>
+ * You must call {@link #release()} when you are done using the camera,
+ * otherwise it will remain locked and be unavailable to other applications.
+ * <p>
+ * Your application should only have one Camera object active at a time for
+ * a particular hardware camera.
+ * <p>
+ * Callbacks from other methods are delivered to the event loop of the
+ * thread which called open(). If this thread has no event loop, then
+ * callbacks are delivered to the main application event loop. If there is
+ * no main application event loop, callbacks are not delivered.
+ * <p class="caution">
+ * <b>Caution:</b> On some devices, this method may take a long time to
+ * complete. It is best to call this method from a worker thread (possibly
+ * using {@link android.os.AsyncTask}) to avoid blocking the main
+ * application UI thread.
+ *
+ * @param cameraId The hardware camera to access, between 0 and
+ * {@link #getNumberOfCameras()}-1.
+ * @param halVersion The HAL API version this camera device to be opened as. When
+ * it is {@value #CAMERA_HAL_API_VERSION_NORMAL_OPEN}, the methods will be equivalent
+ * to {@link #open}, but more detailed error information will be returned to managed code.
+ * @return a new Camera object, connected, locked and ready for use.
+ * @throws RuntimeException if opening the camera fails (for example, if the
+ * camera is in use by another process or device policy manager has disabled
+ * the camera).
+ * @see android.app.admin.DevicePolicyManager#getCameraDisabled(android.content.ComponentName)
+ *
+ * @hide
+ */
+ public static Camera openLegacy(int cameraId, int halVersion) {
+ return new Camera(cameraId, halVersion);
+ }
+
+ /**
+ * Create a legacy camera object.
+ *
+ * @param cameraId The hardware camera to access, between 0 and
+ * {@link #getNumberOfCameras()}-1.
+ * @param halVersion The HAL API version this camera device to be opened as.
+ */
+ private Camera(int cameraId, int halVersion) {
+ int err = cameraInit(cameraId, halVersion);
+ if (checkInitErrors(err)) {
+ switch(err) {
+ case EACCESS:
+ throw new RuntimeException("Fail to connect to camera service");
+ case ENODEV:
+ throw new RuntimeException("Camera initialization failed");
+ case ENOSYS:
+ throw new RuntimeException("Camera initialization failed because some methods"
+ + " are not implemented");
+ case EOPNOTSUPP:
+ throw new RuntimeException("Camera initialization failed because the hal"
+ + " version is not supported by this device");
+ case EINVAL:
+ throw new RuntimeException("Camera initialization failed because the input"
+ + " arugments are invalid");
+ case EBUSY:
+ throw new RuntimeException("Camera initialization failed because the camera"
+ + " device was already opened");
+ case EUSERS:
+ throw new RuntimeException("Camera initialization failed because the max"
+ + " number of camera devices were already opened");
+ default:
+ // Should never hit this.
+ throw new RuntimeException("Unknown camera error");
+ }
+ }
+ }
+
+ private int cameraInit(int cameraId, int halVersion) {
+ // This function should be only called by Camera(int cameraId, int halVersion).
+ if (halVersion < CAMERA_HAL_API_VERSION_1_0 &&
+ halVersion != CAMERA_HAL_API_VERSION_NORMAL_OPEN) {
+ throw new IllegalArgumentException("Invalid HAL version " + halVersion);
+ }
+
+ mShutterCallback = null;
+ mRawImageCallback = null;
+ mJpegCallback = null;
+ mPreviewCallback = null;
+ mPostviewCallback = null;
+ mUsingPreviewAllocation = false;
+ mZoomListener = null;
+
+ Looper looper;
+ if ((looper = Looper.myLooper()) != null) {
+ mEventHandler = new EventHandler(this, looper);
+ } else if ((looper = Looper.getMainLooper()) != null) {
+ mEventHandler = new EventHandler(this, looper);
+ } else {
+ mEventHandler = null;
+ }
+
+ String packageName = ActivityThread.currentPackageName();
+
+ return native_setup(new WeakReference<Camera>(this), cameraId, halVersion, packageName);
+ }
+
Camera(int cameraId) {
int err = cameraInit(cameraId);
if (checkInitErrors(err)) {
@@ -369,7 +495,8 @@ public class Camera {
String packageName = ActivityThread.currentPackageName();
- return native_setup(new WeakReference<Camera>(this), cameraId, packageName);
+ return native_setup(new WeakReference<Camera>(this), cameraId,
+ CAMERA_HAL_API_VERSION_UNSPECIFIED, packageName);
}
/**
@@ -396,7 +523,7 @@ public class Camera {
release();
}
- private native final int native_setup(Object camera_this, int cameraId,
+ private native final int native_setup(Object camera_this, int cameraId, int halVersion,
String packageName);
private native final void native_release();
diff --git a/core/java/android/hardware/ICameraService.aidl b/core/java/android/hardware/ICameraService.aidl
index 31896f5..2bc3dd4 100644
--- a/core/java/android/hardware/ICameraService.aidl
+++ b/core/java/android/hardware/ICameraService.aidl
@@ -74,4 +74,11 @@ interface ICameraService
int getLegacyParameters(int cameraId, out String[] parameters);
// Determines if a particular API version is supported; see ICameraService.h for version defines
int supportsCameraApi(int cameraId, int apiVersion);
+
+ int connectLegacy(ICameraClient client, int cameraId,
+ int halVersion,
+ String clientPackageName,
+ int clientUid,
+ // Container for an ICamera object
+ out BinderHolder device);
}
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 5a02435..dad1854 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -248,7 +248,6 @@ public abstract class CameraMetadata<TKey> {
* <li>Manual frame duration control<ul>
* <li>{@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration}</li>
* <li>{@link CameraCharacteristics#SENSOR_INFO_MAX_FRAME_DURATION android.sensor.info.maxFrameDuration}</li>
- * <li>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap}</li>
* </ul>
* </li>
* <li>Manual exposure control<ul>
@@ -279,6 +278,9 @@ public abstract class CameraMetadata<TKey> {
* result.</p>
* <p>A given camera device may also support additional manual sensor controls,
* but this capability only covers the above list of controls.</p>
+ * <p>If this is supported, {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap} will
+ * additionally return a min frame duration that is greater than
+ * zero for each supported size-format combination.</p>
*
* @see CaptureRequest#BLACK_LEVEL_LOCK
* @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 2cfb611..5bc59dc 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -18,6 +18,7 @@ package android.hardware.camera2;
import android.hardware.camera2.CameraCharacteristics.Key;
import android.hardware.camera2.impl.CameraMetadataNative;
+import android.hardware.camera2.utils.HashCodeHelpers;
import android.hardware.camera2.utils.TypeReference;
import android.os.Parcel;
import android.os.Parcelable;
@@ -280,7 +281,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
@Override
public int hashCode() {
- return mSettings.hashCode();
+ return HashCodeHelpers.hashCode(mSettings, mSurfaceSet, mUserTag);
}
public static final Parcelable.Creator<CaptureRequest> CREATOR =
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index d9f3af4..de1d9a3 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -28,6 +28,8 @@ import android.hardware.camera2.ICameraDeviceUser;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.utils.CameraBinderDecorator;
import android.hardware.camera2.utils.CameraRuntimeException;
+import android.hardware.camera2.utils.CloseableLock;
+import android.hardware.camera2.utils.CloseableLock.ScopedLock;
import android.hardware.camera2.utils.LongParcelable;
import android.os.Handler;
import android.os.IBinder;
@@ -57,13 +59,14 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice {
// TODO: guard every function with if (!mRemoteDevice) check (if it was closed)
private ICameraDeviceUser mRemoteDevice;
- private final Object mLock = new Object();
+ private final CloseableLock mCloseLock;
private final CameraDeviceCallbacks mCallbacks = new CameraDeviceCallbacks();
private final StateListener mDeviceListener;
private volatile StateListener mSessionStateListener;
private final Handler mDeviceHandler;
+ private volatile boolean mClosing = false;
private boolean mInError = false;
private boolean mIdle = true;
@@ -100,7 +103,9 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice {
private final Runnable mCallOnOpened = new Runnable() {
@Override
public void run() {
- if (!CameraDeviceImpl.this.isClosed()) {
+ try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+ if (scopedLock == null) return; // Camera already closed
+
StateListener sessionListener = mSessionStateListener;
if (sessionListener != null) {
sessionListener.onOpened(CameraDeviceImpl.this);
@@ -113,7 +118,9 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice {
private final Runnable mCallOnUnconfigured = new Runnable() {
@Override
public void run() {
- if (!CameraDeviceImpl.this.isClosed()) {
+ try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+ if (scopedLock == null) return; // Camera already closed
+
StateListener sessionListener = mSessionStateListener;
if (sessionListener != null) {
sessionListener.onUnconfigured(CameraDeviceImpl.this);
@@ -126,7 +133,9 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice {
private final Runnable mCallOnActive = new Runnable() {
@Override
public void run() {
- if (!CameraDeviceImpl.this.isClosed()) {
+ try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+ if (scopedLock == null) return; // Camera already closed
+
StateListener sessionListener = mSessionStateListener;
if (sessionListener != null) {
sessionListener.onActive(CameraDeviceImpl.this);
@@ -139,7 +148,9 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice {
private final Runnable mCallOnBusy = new Runnable() {
@Override
public void run() {
- if (!CameraDeviceImpl.this.isClosed()) {
+ try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+ if (scopedLock == null) return; // Camera already closed
+
StateListener sessionListener = mSessionStateListener;
if (sessionListener != null) {
sessionListener.onBusy(CameraDeviceImpl.this);
@@ -150,20 +161,29 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice {
};
private final Runnable mCallOnClosed = new Runnable() {
+ private boolean mClosedOnce = false;
+
@Override
public void run() {
+ if (mClosedOnce) {
+ throw new AssertionError("Don't post #onClosed more than once");
+ }
+
StateListener sessionListener = mSessionStateListener;
if (sessionListener != null) {
sessionListener.onClosed(CameraDeviceImpl.this);
}
mDeviceListener.onClosed(CameraDeviceImpl.this);
+ mClosedOnce = true;
}
};
private final Runnable mCallOnIdle = new Runnable() {
@Override
public void run() {
- if (!CameraDeviceImpl.this.isClosed()) {
+ try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+ if (scopedLock == null) return; // Camera already closed
+
StateListener sessionListener = mSessionStateListener;
if (sessionListener != null) {
sessionListener.onIdle(CameraDeviceImpl.this);
@@ -176,7 +196,9 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice {
private final Runnable mCallOnDisconnected = new Runnable() {
@Override
public void run() {
- if (!CameraDeviceImpl.this.isClosed()) {
+ try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+ if (scopedLock == null) return; // Camera already closed
+
StateListener sessionListener = mSessionStateListener;
if (sessionListener != null) {
sessionListener.onDisconnected(CameraDeviceImpl.this);
@@ -195,6 +217,7 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice {
mDeviceListener = listener;
mDeviceHandler = handler;
mCharacteristics = characteristics;
+ mCloseLock = new CloseableLock(/*name*/"CD-" + mCameraId);
final int MAX_TAG_LEN = 23;
String tag = String.format("CameraDevice-JV-%s", mCameraId);
@@ -210,8 +233,8 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice {
}
public void setRemoteDevice(ICameraDeviceUser remoteDevice) {
+ try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
// TODO: Move from decorator to direct binder-mediated exceptions
- synchronized(mLock) {
// If setRemoteFailure already called, do nothing
if (mInError) return;
@@ -254,7 +277,9 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice {
}
final int code = failureCode;
final boolean isError = failureIsError;
- synchronized (mLock) {
+ try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+ if (scopedLock == null) return; // Camera already closed, can't go to error state
+
mInError = true;
mDeviceHandler.post(new Runnable() {
@Override
@@ -280,7 +305,7 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice {
if (outputs == null) {
outputs = new ArrayList<Surface>();
}
- synchronized (mLock) {
+ try (ScopedLock scopedLock = mCloseLock.acquireExclusiveLock()) {
checkIfCameraClosedOrInError();
HashSet<Surface> addSet = new HashSet<Surface>(outputs); // Streams to create
@@ -344,7 +369,7 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice {
public void createCaptureSession(List<Surface> outputs,
CameraCaptureSession.StateListener listener, Handler handler)
throws CameraAccessException {
- synchronized (mLock) {
+ try (ScopedLock scopedLock = mCloseLock.acquireExclusiveLock()) {
if (DEBUG) {
Log.d(TAG, "createCaptureSession");
}
@@ -389,7 +414,7 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice {
@Override
public CaptureRequest.Builder createCaptureRequest(int templateType)
throws CameraAccessException {
- synchronized (mLock) {
+ try (ScopedLock scopedLock = mCloseLock.acquireExclusiveLock()) {
checkIfCameraClosedOrInError();
CameraMetadataNative templatedRequest = new CameraMetadataNative();
@@ -509,7 +534,7 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice {
handler = checkHandler(handler);
}
- synchronized (mLock) {
+ try (ScopedLock scopedLock = mCloseLock.acquireExclusiveLock()) {
checkIfCameraClosedOrInError();
int requestId;
@@ -581,7 +606,7 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice {
@Override
public void stopRepeating() throws CameraAccessException {
- synchronized (mLock) {
+ try (ScopedLock scopedLock = mCloseLock.acquireExclusiveLock()) {
checkIfCameraClosedOrInError();
if (mRepeatingRequestId != REQUEST_ID_NONE) {
@@ -612,7 +637,7 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice {
private void waitUntilIdle() throws CameraAccessException {
- synchronized (mLock) {
+ try (ScopedLock scopedLock = mCloseLock.acquireExclusiveLock()) {
checkIfCameraClosedOrInError();
if (mRepeatingRequestId != REQUEST_ID_NONE) {
throw new IllegalStateException("Active repeating request ongoing");
@@ -633,7 +658,7 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice {
@Override
public void flush() throws CameraAccessException {
- synchronized (mLock) {
+ try (ScopedLock scopedLock = mCloseLock.acquireExclusiveLock()) {
checkIfCameraClosedOrInError();
mDeviceHandler.post(mCallOnBusy);
@@ -656,8 +681,15 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice {
@Override
public void close() {
- synchronized (mLock) {
+ mClosing = true;
+ // Acquire exclusive lock, close, release (idempotent)
+ mCloseLock.close();
+ /*
+ * The rest of this is safe, since no other methods will be able to execute
+ * (they will throw ISE instead; the callbacks will get dropped)
+ */
+ {
try {
if (mRemoteDevice != null) {
mRemoteDevice.disconnect();
@@ -805,7 +837,12 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice {
// remove request from mCaptureListenerMap
final int requestId = frameNumberRequestPair.getValue();
final CaptureListenerHolder holder;
- synchronized (mLock) {
+ try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+ if (scopedLock == null) {
+ Log.w(TAG, "Camera closed while checking sequences");
+ return;
+ }
+
int index = mCaptureListenerMap.indexOfKey(requestId);
holder = (index >= 0) ? mCaptureListenerMap.valueAt(index)
: null;
@@ -890,9 +927,12 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice {
@Override
public void onCameraError(final int errorCode, CaptureResultExtras resultExtras) {
Runnable r = null;
- if (isClosed()) return;
- synchronized(mLock) {
+ try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+ if (scopedLock == null) {
+ return; // Camera already closed
+ }
+
mInError = true;
switch (errorCode) {
case ERROR_CAMERA_DISCONNECTED:
@@ -914,25 +954,24 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice {
break;
}
CameraDeviceImpl.this.mDeviceHandler.post(r);
- }
- // Fire onCaptureSequenceCompleted
- if (DEBUG) {
- Log.v(TAG, String.format("got error frame %d", resultExtras.getFrameNumber()));
+ // Fire onCaptureSequenceCompleted
+ if (DEBUG) {
+ Log.v(TAG, String.format("got error frame %d", resultExtras.getFrameNumber()));
+ }
+ mFrameNumberTracker.updateTracker(resultExtras.getFrameNumber(), /*error*/true);
+ checkAndFireSequenceComplete();
}
- mFrameNumberTracker.updateTracker(resultExtras.getFrameNumber(), /*error*/true);
- checkAndFireSequenceComplete();
-
}
@Override
public void onCameraIdle() {
- if (isClosed()) return;
-
if (DEBUG) {
Log.d(TAG, "Camera now idle");
}
- synchronized (mLock) {
+ try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+ if (scopedLock == null) return; // Camera already closed
+
if (!CameraDeviceImpl.this.mIdle) {
CameraDeviceImpl.this.mDeviceHandler.post(mCallOnIdle);
}
@@ -948,30 +987,33 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice {
}
final CaptureListenerHolder holder;
- // Get the listener for this frame ID, if there is one
- synchronized (mLock) {
+ try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+ if (scopedLock == null) return; // Camera already closed
+
+ // Get the listener for this frame ID, if there is one
holder = CameraDeviceImpl.this.mCaptureListenerMap.get(requestId);
- }
- if (holder == null) {
- return;
- }
+ if (holder == null) {
+ return;
+ }
- if (isClosed()) return;
+ if (isClosed()) return;
- // Dispatch capture start notice
- holder.getHandler().post(
- new Runnable() {
- @Override
- public void run() {
- if (!CameraDeviceImpl.this.isClosed()) {
- holder.getListener().onCaptureStarted(
- CameraDeviceImpl.this,
- holder.getRequest(resultExtras.getSubsequenceId()),
- timestamp);
+ // Dispatch capture start notice
+ holder.getHandler().post(
+ new Runnable() {
+ @Override
+ public void run() {
+ if (!CameraDeviceImpl.this.isClosed()) {
+ holder.getListener().onCaptureStarted(
+ CameraDeviceImpl.this,
+ holder.getRequest(resultExtras.getSubsequenceId()),
+ timestamp);
+ }
}
- }
- });
+ });
+
+ }
}
@Override
@@ -984,88 +1026,91 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice {
+ requestId);
}
+ try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
+ if (scopedLock == null) return; // Camera already closed
- // TODO: Handle CameraCharacteristics access from CaptureResult correctly.
- result.set(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE,
- getCharacteristics().get(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE));
+ // TODO: Handle CameraCharacteristics access from CaptureResult correctly.
+ result.set(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE,
+ getCharacteristics().get(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE));
- final CaptureListenerHolder holder;
- synchronized (mLock) {
- holder = CameraDeviceImpl.this.mCaptureListenerMap.get(requestId);
- }
+ final CaptureListenerHolder holder =
+ CameraDeviceImpl.this.mCaptureListenerMap.get(requestId);
- Boolean quirkPartial = result.get(CaptureResult.QUIRKS_PARTIAL_RESULT);
- boolean quirkIsPartialResult = (quirkPartial != null && quirkPartial);
+ Boolean quirkPartial = result.get(CaptureResult.QUIRKS_PARTIAL_RESULT);
+ boolean quirkIsPartialResult = (quirkPartial != null && quirkPartial);
- // Update tracker (increment counter) when it's not a partial result.
- if (!quirkIsPartialResult) {
- mFrameNumberTracker.updateTracker(resultExtras.getFrameNumber(), /*error*/false);
- }
+ // Update tracker (increment counter) when it's not a partial result.
+ if (!quirkIsPartialResult) {
+ mFrameNumberTracker.updateTracker(resultExtras.getFrameNumber(),
+ /*error*/false);
+ }
- // Check if we have a listener for this
- if (holder == null) {
- if (DEBUG) {
- Log.d(TAG,
- "holder is null, early return at frame "
- + resultExtras.getFrameNumber());
+ // Check if we have a listener for this
+ if (holder == null) {
+ if (DEBUG) {
+ Log.d(TAG,
+ "holder is null, early return at frame "
+ + resultExtras.getFrameNumber());
+ }
+ return;
}
- return;
- }
- if (isClosed()) {
- if (DEBUG) {
- Log.d(TAG,
- "camera is closed, early return at frame "
- + resultExtras.getFrameNumber());
+ if (isClosed()) {
+ if (DEBUG) {
+ Log.d(TAG,
+ "camera is closed, early return at frame "
+ + resultExtras.getFrameNumber());
+ }
+ return;
}
- return;
- }
- final CaptureRequest request = holder.getRequest(resultExtras.getSubsequenceId());
+ final CaptureRequest request = holder.getRequest(resultExtras.getSubsequenceId());
- Runnable resultDispatch = null;
+ Runnable resultDispatch = null;
- // Either send a partial result or the final capture completed result
- if (quirkIsPartialResult) {
- final CaptureResult resultAsCapture =
- new CaptureResult(result, request, requestId);
+ // Either send a partial result or the final capture completed result
+ if (quirkIsPartialResult) {
+ final CaptureResult resultAsCapture =
+ new CaptureResult(result, request, requestId);
- // Partial result
- resultDispatch = new Runnable() {
- @Override
- public void run() {
- if (!CameraDeviceImpl.this.isClosed()){
- holder.getListener().onCapturePartial(
- CameraDeviceImpl.this,
- request,
- resultAsCapture);
+ // Partial result
+ resultDispatch = new Runnable() {
+ @Override
+ public void run() {
+ if (!CameraDeviceImpl.this.isClosed()){
+ holder.getListener().onCapturePartial(
+ CameraDeviceImpl.this,
+ request,
+ resultAsCapture);
+ }
}
- }
- };
- } else {
- final TotalCaptureResult resultAsCapture =
- new TotalCaptureResult(result, request, requestId);
+ };
+ } else {
+ final TotalCaptureResult resultAsCapture =
+ new TotalCaptureResult(result, request, requestId);
- // Final capture result
- resultDispatch = new Runnable() {
- @Override
- public void run() {
- if (!CameraDeviceImpl.this.isClosed()){
- holder.getListener().onCaptureCompleted(
- CameraDeviceImpl.this,
- request,
- resultAsCapture);
+ // Final capture result
+ resultDispatch = new Runnable() {
+ @Override
+ public void run() {
+ if (!CameraDeviceImpl.this.isClosed()){
+ holder.getListener().onCaptureCompleted(
+ CameraDeviceImpl.this,
+ request,
+ resultAsCapture);
+ }
}
- }
- };
- }
+ };
+ }
- holder.getHandler().post(resultDispatch);
+ holder.getHandler().post(resultDispatch);
+
+ // Fire onCaptureSequenceCompleted
+ if (!quirkIsPartialResult) {
+ checkAndFireSequenceComplete();
+ }
- // Fire onCaptureSequenceCompleted
- if (!quirkIsPartialResult) {
- checkAndFireSequenceComplete();
}
}
@@ -1101,10 +1146,9 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice {
}
}
+ /** Whether the camera device has started to close (may not yet have finished) */
private boolean isClosed() {
- synchronized(mLock) {
- return (mRemoteDevice == null);
- }
+ return mClosing;
}
private CameraCharacteristics getCharacteristics() {
diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
index 54d9c3c..e4412ce 100644
--- a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
+++ b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
@@ -169,7 +169,7 @@ public class CameraDeviceUserShim implements ICameraDeviceUser {
}
int numSurfaces = mSurfaces.size();
if (numSurfaces > 0) {
- surfaces = new ArrayList<Surface>();
+ surfaces = new ArrayList<>();
for (int i = 0; i < numSurfaces; ++i) {
surfaces.add(mSurfaces.valueAt(i));
}
diff --git a/core/java/android/hardware/camera2/legacy/GLThreadManager.java b/core/java/android/hardware/camera2/legacy/GLThreadManager.java
index 0382557..5d44fd2 100644
--- a/core/java/android/hardware/camera2/legacy/GLThreadManager.java
+++ b/core/java/android/hardware/camera2/legacy/GLThreadManager.java
@@ -100,6 +100,7 @@ public class GLThreadManager {
break;
case MSG_ALLOW_FRAMES:
mDroppingFrames = false;
+ break;
default:
Log.e(TAG, "Unhandled message " + msg.what + " on GLThread.");
break;
diff --git a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
index c34a34d..50515a2 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
@@ -23,7 +23,6 @@ import android.hardware.camera2.impl.CaptureResultExtras;
import android.hardware.camera2.ICameraDeviceCallbacks;
import android.hardware.camera2.utils.LongParcelable;
import android.hardware.camera2.impl.CameraMetadataNative;
-import android.hardware.camera2.utils.CameraBinderDecorator;
import android.hardware.camera2.utils.CameraRuntimeException;
import android.os.ConditionVariable;
import android.os.Handler;
@@ -34,7 +33,8 @@ import android.view.Surface;
import java.util.ArrayList;
import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
+
+import static android.hardware.camera2.utils.CameraBinderDecorator.*;
/**
* This class emulates the functionality of a Camera2 device using a the old Camera class.
@@ -50,9 +50,11 @@ public class LegacyCameraDevice implements AutoCloseable {
public static final String DEBUG_PROP = "HAL1ShimLogging";
private final String TAG;
+ private static final boolean DEBUG = false;
private final int mCameraId;
private final ICameraDeviceCallbacks mDeviceCallbacks;
private final CameraDeviceState mDeviceState = new CameraDeviceState();
+ private List<Surface> mConfiguredSurfaces;
private final ConditionVariable mIdle = new ConditionVariable(/*open*/true);
@@ -84,6 +86,9 @@ public class LegacyCameraDevice implements AutoCloseable {
mResultHandler.post(new Runnable() {
@Override
public void run() {
+ if (DEBUG) {
+ Log.d(TAG, "doing onError callback.");
+ }
try {
mDeviceCallbacks.onCameraError(errorCode, extras);
} catch (RemoteException e) {
@@ -92,13 +97,14 @@ public class LegacyCameraDevice implements AutoCloseable {
}
}
});
-
-
}
@Override
public void onConfiguring() {
// Do nothing
+ if (DEBUG) {
+ Log.d(TAG, "doing onConfiguring callback.");
+ }
}
@Override
@@ -108,6 +114,9 @@ public class LegacyCameraDevice implements AutoCloseable {
mResultHandler.post(new Runnable() {
@Override
public void run() {
+ if (DEBUG) {
+ Log.d(TAG, "doing onIdle callback.");
+ }
try {
mDeviceCallbacks.onCameraIdle();
} catch (RemoteException e) {
@@ -126,6 +135,9 @@ public class LegacyCameraDevice implements AutoCloseable {
mResultHandler.post(new Runnable() {
@Override
public void run() {
+ if (DEBUG) {
+ Log.d(TAG, "doing onCaptureStarted callback.");
+ }
try {
// TODO: Don't fake timestamp
mDeviceCallbacks.onCaptureStarted(extras, timestamp);
@@ -135,7 +147,6 @@ public class LegacyCameraDevice implements AutoCloseable {
}
}
});
-
}
@Override
@@ -145,6 +156,9 @@ public class LegacyCameraDevice implements AutoCloseable {
mResultHandler.post(new Runnable() {
@Override
public void run() {
+ if (DEBUG) {
+ Log.d(TAG, "doing onCaptureResult callback.");
+ }
try {
// TODO: Don't fake metadata
mDeviceCallbacks.onResultReceived(result, extras);
@@ -200,16 +214,35 @@ public class LegacyCameraDevice implements AutoCloseable {
/**
* Configure the device with a set of output surfaces.
*
+ * <p>Using empty or {@code null} {@code outputs} is the same as unconfiguring.</p>
+ *
+ * <p>Every surface in {@code outputs} must be non-{@code null}.</p>
+ *
* @param outputs a list of surfaces to set.
- * @return an error code for this binder operation, or {@link CameraBinderDecorator.NO_ERROR}
+ * @return an error code for this binder operation, or {@link NO_ERROR}
* on success.
*/
public int configureOutputs(List<Surface> outputs) {
+ if (outputs != null) {
+ for (Surface output : outputs) {
+ if (output == null) {
+ Log.e(TAG, "configureOutputs - null outputs are not allowed");
+ return BAD_VALUE;
+ }
+ }
+ }
+
int error = mDeviceState.setConfiguring();
- if (error == CameraBinderDecorator.NO_ERROR) {
+ if (error == NO_ERROR) {
mRequestThreadManager.configure(outputs);
error = mDeviceState.setIdle();
}
+
+ // TODO: May also want to check the surfaces more deeply (e.g. state, formats, sizes..)
+ if (error == NO_ERROR) {
+ mConfiguredSurfaces = outputs != null ? new ArrayList<>(outputs) : null;
+ }
+
return error;
}
@@ -226,7 +259,35 @@ public class LegacyCameraDevice implements AutoCloseable {
*/
public int submitRequestList(List<CaptureRequest> requestList, boolean repeating,
/*out*/LongParcelable frameNumber) {
- // TODO: validate request here
+ if (requestList == null || requestList.isEmpty()) {
+ Log.e(TAG, "submitRequestList - Empty/null requests are not allowed");
+ return BAD_VALUE;
+ }
+
+ // Make sure that there all requests have at least 1 surface; all surfaces are non-null
+ for (CaptureRequest request : requestList) {
+ if (request.getTargets().isEmpty()) {
+ Log.e(TAG, "submitRequestList - "
+ + "Each request must have at least one Surface target");
+ return BAD_VALUE;
+ }
+
+ for (Surface surface : request.getTargets()) {
+ if (surface == null) {
+ Log.e(TAG, "submitRequestList - Null Surface targets are not allowed");
+ return BAD_VALUE;
+ } else if (mConfiguredSurfaces == null) {
+ Log.e(TAG, "submitRequestList - must configure " +
+ " device with valid surfaces before submitting requests");
+ return INVALID_OPERATION;
+ } else if (!mConfiguredSurfaces.contains(surface)) {
+ Log.e(TAG, "submitRequestList - cannot use a surface that wasn't configured");
+ return BAD_VALUE;
+ }
+ }
+ }
+
+ // TODO: further validation of request here
mIdle.close();
return mRequestThreadManager.submitCaptureRequests(requestList, repeating,
frameNumber);
diff --git a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
index 8bb066f..6fa2134 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
@@ -21,12 +21,16 @@ import android.hardware.Camera;
import android.hardware.Camera.CameraInfo;
import android.hardware.Camera.Size;
import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.params.StreamConfiguration;
import android.hardware.camera2.params.StreamConfigurationDuration;
import android.util.Log;
+import android.util.Range;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import static com.android.internal.util.Preconditions.*;
@@ -44,6 +48,11 @@ public class LegacyMetadataMapper {
private static final int HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED = 0x22;
private static final int HAL_PIXEL_FORMAT_BLOB = 0x21;
+ private static final long APPROXIMATE_CAPTURE_DELAY_MS = 200; // ms
+ private static final long APPROXIMATE_SENSOR_AREA = (1 << 20); // 8mp
+ private static final long APPROXIMATE_JPEG_ENCODE_TIME = 600; // ms
+ private static final long NS_PER_MS = 1000000;
+
/**
* Create characteristics for a legacy device by mapping the {@code parameters}
* and {@code info}
@@ -86,14 +95,13 @@ public class LegacyMetadataMapper {
private static void mapCameraParameters(CameraMetadataNative m, Camera.Parameters p) {
mapStreamConfigs(m, p);
+ mapAeConfig(m, p);
+ mapCapabilities(m, p);
// TODO: map other fields
}
private static void mapStreamConfigs(CameraMetadataNative m, Camera.Parameters p) {
- // TODO: set non-empty durations
- m.set(SCALER_AVAILABLE_MIN_FRAME_DURATIONS, new StreamConfigurationDuration[] {} );
- m.set(SCALER_AVAILABLE_STALL_DURATIONS, new StreamConfigurationDuration[] {} );
ArrayList<StreamConfiguration> availableStreamConfigs = new ArrayList<>();
/*
@@ -122,10 +130,74 @@ public class LegacyMetadataMapper {
String.format("mapStreamConfigs - Skipping non-public format %x", format));
}
}
+
+ List<Camera.Size> jpegSizes = p.getSupportedPictureSizes();
appendStreamConfig(availableStreamConfigs,
HAL_PIXEL_FORMAT_BLOB, p.getSupportedPictureSizes());
m.set(SCALER_AVAILABLE_STREAM_CONFIGURATIONS,
availableStreamConfigs.toArray(new StreamConfiguration[0]));
+
+ // No frame durations available
+ m.set(SCALER_AVAILABLE_MIN_FRAME_DURATIONS, new StreamConfigurationDuration[0]);
+
+ StreamConfigurationDuration[] jpegStalls =
+ new StreamConfigurationDuration[jpegSizes.size()];
+ int i = 0;
+ long longestStallDuration = -1;
+ for (Camera.Size s : jpegSizes) {
+ long stallDuration = calculateJpegStallDuration(s);
+ jpegStalls[i++] = new StreamConfigurationDuration(HAL_PIXEL_FORMAT_BLOB, s.width,
+ s.height, stallDuration);
+ if (longestStallDuration < stallDuration) {
+ longestStallDuration = stallDuration;
+ }
+ }
+ // Set stall durations for jpeg, other formats use default stall duration
+ m.set(SCALER_AVAILABLE_STALL_DURATIONS, jpegStalls);
+
+ m.set(SENSOR_INFO_MAX_FRAME_DURATION, longestStallDuration);
+ }
+
+ @SuppressWarnings({"unchecked"})
+ private static void mapAeConfig(CameraMetadataNative m, Camera.Parameters p) {
+
+ List<int[]> fpsRanges = p.getSupportedPreviewFpsRange();
+ if (fpsRanges == null) {
+ throw new AssertionError("Supported FPS ranges cannot be null.");
+ }
+ int rangesSize = fpsRanges.size();
+ if (rangesSize <= 0) {
+ throw new AssertionError("At least one FPS range must be supported.");
+ }
+ Range<Integer>[] ranges = new Range[rangesSize];
+ int i = 0;
+ for (int[] r : fpsRanges) {
+ ranges[i++] = Range.create(r[Camera.Parameters.PREVIEW_FPS_MIN_INDEX],
+ r[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]);
+ }
+ m.set(CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES, ranges);
+
+ List<String> antiBandingModes = p.getSupportedAntibanding();
+ int antiBandingModesSize = antiBandingModes.size();
+ if (antiBandingModesSize > 0) {
+ int[] modes = new int[antiBandingModesSize];
+ int j = 0;
+ for (String mode : antiBandingModes) {
+ int convertedMode = convertAntiBandingMode(mode);
+ if (convertedMode == -1) {
+ Log.w(TAG, "Antibanding mode " + ((mode == null) ? "NULL" : mode) +
+ " not supported, skipping...");
+ } else {
+ modes[j++] = convertedMode;
+ }
+ }
+ m.set(CONTROL_AE_AVAILABLE_ANTIBANDING_MODES, Arrays.copyOf(modes, j));
+ }
+ }
+
+ private static void mapCapabilities(CameraMetadataNative m, Camera.Parameters p) {
+ int[] capabilities = { REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE };
+ m.set(REQUEST_AVAILABLE_CAPABILITIES, capabilities);
}
private static void appendStreamConfig(
@@ -136,4 +208,115 @@ public class LegacyMetadataMapper {
configs.add(config);
}
}
+
+ /**
+ * Returns -1 if the anti-banding mode string is null, or not supported.
+ */
+ private static int convertAntiBandingMode(final String mode) {
+ if (mode == null) {
+ return -1;
+ }
+ switch(mode) {
+ case Camera.Parameters.ANTIBANDING_OFF: {
+ return CONTROL_AE_ANTIBANDING_MODE_OFF;
+ }
+ case Camera.Parameters.ANTIBANDING_50HZ: {
+ return CONTROL_AE_ANTIBANDING_MODE_50HZ;
+ }
+ case Camera.Parameters.ANTIBANDING_60HZ: {
+ return CONTROL_AE_ANTIBANDING_MODE_60HZ;
+ }
+ case Camera.Parameters.ANTIBANDING_AUTO: {
+ return CONTROL_AE_ANTIBANDING_MODE_AUTO;
+ }
+ default: {
+ return -1;
+ }
+ }
+ }
+
+ /**
+ * Returns null if the anti-banding mode enum is not supported.
+ */
+ private static String convertAntiBandingModeToLegacy(int mode) {
+ switch(mode) {
+ case CONTROL_AE_ANTIBANDING_MODE_OFF: {
+ return Camera.Parameters.ANTIBANDING_OFF;
+ }
+ case CONTROL_AE_ANTIBANDING_MODE_50HZ: {
+ return Camera.Parameters.ANTIBANDING_50HZ;
+ }
+ case CONTROL_AE_ANTIBANDING_MODE_60HZ: {
+ return Camera.Parameters.ANTIBANDING_60HZ;
+ }
+ case CONTROL_AE_ANTIBANDING_MODE_AUTO: {
+ return Camera.Parameters.ANTIBANDING_AUTO;
+ }
+ default: {
+ return null;
+ }
+ }
+ }
+
+
+ private static int[] convertAeFpsRangeToLegacy(Range<Integer> fpsRange) {
+ int[] legacyFps = new int[2];
+ legacyFps[Camera.Parameters.PREVIEW_FPS_MIN_INDEX] = fpsRange.getLower();
+ legacyFps[Camera.Parameters.PREVIEW_FPS_MAX_INDEX] = fpsRange.getUpper();
+ return legacyFps;
+ }
+
+ /**
+ * Return the stall duration for a given output jpeg size in nanoseconds.
+ *
+ * <p>An 8mp image is chosen to have a stall duration of 0.8 seconds.</p>
+ */
+ private static long calculateJpegStallDuration(Camera.Size size) {
+ long baseDuration = APPROXIMATE_CAPTURE_DELAY_MS * NS_PER_MS; // 200ms for capture
+ long area = size.width * (long) size.height;
+ long stallPerArea = APPROXIMATE_JPEG_ENCODE_TIME * NS_PER_MS /
+ APPROXIMATE_SENSOR_AREA; // 600ms stall for 8mp
+ return baseDuration + area * stallPerArea;
+ }
+
+ /**
+ * Generate capture result metadata from legacy camera parameters.
+ *
+ * @param params a {@link Camera.Parameters} object to generate metadata from.
+ * @param request the {@link CaptureRequest} used for this result.
+ * @param timestamp the timestamp to use for this result in nanoseconds.
+ * @return a {@link CameraMetadataNative} object containing result metadata.
+ */
+ public static CameraMetadataNative convertResultMetadata(Camera.Parameters params,
+ CaptureRequest request,
+ long timestamp) {
+ CameraMetadataNative result = new CameraMetadataNative();
+ result.set(CaptureResult.LENS_FOCAL_LENGTH, params.getFocalLength());
+ result.set(CaptureResult.SENSOR_TIMESTAMP, timestamp);
+
+ // TODO: Remaining result metadata tags conversions.
+ return result;
+ }
+
+ /**
+ * Set the legacy parameters using the request metadata.
+ *
+ * @param request a {@link CaptureRequest} object to generate parameters from.
+ * @param params the a {@link Camera.Parameters} to set parameters in.
+ */
+ public static void convertRequestMetadata(CaptureRequest request,
+ /*out*/Camera.Parameters params) {
+ Integer antiBandingMode = request.get(CaptureRequest.CONTROL_AE_ANTIBANDING_MODE);
+ if (antiBandingMode != null) {
+ String legacyMode = convertAntiBandingModeToLegacy(antiBandingMode);
+ if (legacyMode != null) params.setAntibanding(legacyMode);
+ }
+
+ Range<Integer> aeFpsRange = request.get(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE);
+ if (aeFpsRange != null) {
+ int[] legacyFps = convertAeFpsRangeToLegacy(aeFpsRange);
+ params.setPreviewFpsRange(legacyFps[Camera.Parameters.PREVIEW_FPS_MIN_INDEX],
+ legacyFps[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]);
+ }
+ }
}
diff --git a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
index 7b522ff..e0f3429 100644
--- a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
+++ b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
@@ -16,11 +16,9 @@
package android.hardware.camera2.legacy;
-import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.hardware.camera2.CaptureRequest;
-import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.utils.LongParcelable;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.os.ConditionVariable;
@@ -71,21 +69,26 @@ public class RequestThreadManager {
private static final float ASPECT_RATIO_TOLERANCE = 0.01f;
private boolean mPreviewRunning = false;
+ private volatile long mLastJpegTimestamp;
+ private volatile long mLastPreviewTimestamp;
private volatile RequestHolder mInFlightPreview;
private volatile RequestHolder mInFlightJpeg;
- private List<Surface> mPreviewOutputs = new ArrayList<Surface>();
- private List<Surface> mCallbackOutputs = new ArrayList<Surface>();
+ private final List<Surface> mPreviewOutputs = new ArrayList<Surface>();
+ private final List<Surface> mCallbackOutputs = new ArrayList<Surface>();
private GLThreadManager mGLThreadManager;
private SurfaceTexture mPreviewTexture;
+ private Camera.Parameters mParams;
private Size mIntermediateBufferSize;
private final RequestQueue mRequestQueue = new RequestQueue();
+ private CaptureRequest mLastRequest = null;
private SurfaceTexture mDummyTexture;
private Surface mDummySurface;
private final FpsCounter mPrevCounter = new FpsCounter("Incoming Preview");
+ private final FpsCounter mRequestCounter = new FpsCounter("Incoming Requests");
/**
* Container object for Configure messages.
@@ -209,23 +212,34 @@ public class RequestThreadManager {
}
};
+ private final Camera.ShutterCallback mJpegShutterCallback = new Camera.ShutterCallback() {
+ @Override
+ public void onShutter() {
+ mLastJpegTimestamp = SystemClock.elapsedRealtimeNanos();
+ }
+ };
+
private final SurfaceTexture.OnFrameAvailableListener mPreviewCallback =
new SurfaceTexture.OnFrameAvailableListener() {
@Override
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
- if (DEBUG) {
- mPrevCounter.countAndLog();
- }
RequestHolder holder = mInFlightPreview;
if (holder == null) {
+ mGLThreadManager.queueNewFrame(null);
Log.w(TAG, "Dropping preview frame.");
- mInFlightPreview = null;
return;
}
+
+ if (DEBUG) {
+ mPrevCounter.countAndLog();
+ }
+ mInFlightPreview = null;
+
if (holder.hasPreviewTargets()) {
mGLThreadManager.queueNewFrame(holder.getHolderTargets());
}
+ mLastPreviewTimestamp = surfaceTexture.getTimestamp();
mReceivedPreview.open();
}
};
@@ -252,7 +266,7 @@ public class RequestThreadManager {
}
mInFlightJpeg = request;
// TODO: Hook up shutter callback to CameraDeviceStateListener#onCaptureStarted
- mCamera.takePicture(/*shutter*/null, /*raw*/null, mJpegCallback);
+ mCamera.takePicture(mJpegShutterCallback, /*raw*/null, mJpegCallback);
mPreviewRunning = false;
}
@@ -300,19 +314,20 @@ public class RequestThreadManager {
mInFlightPreview = null;
mInFlightJpeg = null;
-
- for (Surface s : outputs) {
- int format = LegacyCameraDevice.nativeDetectSurfaceType(s);
- switch (format) {
- case CameraMetadataNative.NATIVE_JPEG_FORMAT:
- mCallbackOutputs.add(s);
- break;
- default:
- mPreviewOutputs.add(s);
- break;
+ if (outputs != null) {
+ for (Surface s : outputs) {
+ int format = LegacyCameraDevice.nativeDetectSurfaceType(s);
+ switch (format) {
+ case CameraMetadataNative.NATIVE_JPEG_FORMAT:
+ mCallbackOutputs.add(s);
+ break;
+ default:
+ mPreviewOutputs.add(s);
+ break;
+ }
}
}
-
+ mParams = mCamera.getParameters();
if (mPreviewOutputs.size() > 0) {
List<Size> outputSizes = new ArrayList<>(outputs.size());
for (Surface s : mPreviewOutputs) {
@@ -323,13 +338,11 @@ public class RequestThreadManager {
Size largestOutput = findLargestByArea(outputSizes);
- Camera.Parameters params = mCamera.getParameters();
-
// Find largest jpeg dimension - assume to have the same aspect ratio as sensor.
- List<Size> supportedJpegSizes = convertSizeList(params.getSupportedPictureSizes());
+ List<Size> supportedJpegSizes = convertSizeList(mParams.getSupportedPictureSizes());
Size largestJpegDimen = findLargestByArea(supportedJpegSizes);
- List<Size> supportedPreviewSizes = convertSizeList(params.getSupportedPreviewSizes());
+ List<Size> supportedPreviewSizes = convertSizeList(mParams.getSupportedPreviewSizes());
// Use smallest preview dimension with same aspect ratio as sensor that is >= than all
// of the configured output dimensions. If none exists, fall back to using the largest
@@ -357,8 +370,6 @@ public class RequestThreadManager {
}
}
-
-
// TODO: Detect and optimize single-output paths here to skip stream teeing.
if (mGLThreadManager == null) {
mGLThreadManager = new GLThreadManager(mCameraId);
@@ -419,7 +430,6 @@ public class RequestThreadManager {
private final Handler.Callback mRequestHandlerCb = new Handler.Callback() {
private boolean mCleanup = false;
- private List<RequestHolder> mRepeating = null;
@SuppressWarnings("unchecked")
@Override
@@ -428,10 +438,14 @@ public class RequestThreadManager {
return true;
}
+ if (DEBUG) {
+ Log.d(TAG, "Request thread handling message:" + msg.what);
+ }
switch (msg.what) {
case MSG_CONFIGURE_OUTPUTS:
ConfigureHolder config = (ConfigureHolder) msg.obj;
- Log.i(TAG, "Configure outputs: " + config.surfaces.size() +
+ int sizes = config.surfaces != null ? config.surfaces.size() : 0;
+ Log.i(TAG, "Configure outputs: " + sizes +
" surfaces configured.");
try {
configureOutputs(config.surfaces);
@@ -459,7 +473,15 @@ public class RequestThreadManager {
List<RequestHolder> requests =
nextBurst.first.produceRequestHolders(nextBurst.second);
for (RequestHolder holder : requests) {
+ CaptureRequest request = holder.getRequest();
+ if (mLastRequest == null || mLastRequest != request) {
+ mLastRequest = request;
+ LegacyMetadataMapper.convertRequestMetadata(mLastRequest,
+ /*out*/mParams);
+ mCamera.setParameters(mParams);
+ }
mDeviceState.setCaptureStart(holder);
+ long timestamp = 0;
try {
if (holder.hasPreviewTargets()) {
mReceivedPreview.close();
@@ -468,6 +490,7 @@ public class RequestThreadManager {
// TODO: report error to CameraDevice
Log.e(TAG, "Hit timeout for preview callback!");
}
+ timestamp = mLastPreviewTimestamp;
}
if (holder.hasJpegTargets()) {
mReceivedJpeg.close();
@@ -478,16 +501,19 @@ public class RequestThreadManager {
Log.e(TAG, "Hit timeout for jpeg callback!");
}
mInFlightJpeg = null;
+ timestamp = mLastJpegTimestamp;
}
} catch (IOException e) {
// TODO: err handling
throw new IOError(e);
}
- Camera.Parameters params = mCamera.getParameters();
- CameraMetadataNative result = convertResultMetadata(params,
- holder.getRequest());
+ CameraMetadataNative result = LegacyMetadataMapper.convertResultMetadata(mParams,
+ request, timestamp);
mDeviceState.setCaptureResult(holder, result);
}
+ if (DEBUG) {
+ mRequestCounter.countAndLog();
+ }
break;
case MSG_CLEANUP:
mCleanup = true;
@@ -506,15 +532,6 @@ public class RequestThreadManager {
}
};
- private CameraMetadataNative convertResultMetadata(Camera.Parameters params,
- CaptureRequest request) {
- CameraMetadataNative result = new CameraMetadataNative();
- result.set(CaptureResult.LENS_FOCAL_LENGTH, params.getFocalLength());
-
- // TODO: Remaining result metadata tags conversions.
- return result;
- }
-
/**
* Create a new RequestThreadManager.
*
@@ -597,12 +614,14 @@ public class RequestThreadManager {
/**
- * Configure with the current output Surfaces.
+ * Configure with the current list of output Surfaces.
*
* <p>
* This operation blocks until the configuration is complete.
* </p>
*
+ * <p>Using a {@code null} or empty {@code outputs} list is the equivalent of unconfiguring.</p>
+ *
* @param outputs a {@link java.util.Collection} of outputs to configure.
*/
public void configure(Collection<Surface> outputs) {
diff --git a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
index 49f419f..e9d32f0 100644
--- a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
+++ b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
@@ -486,6 +486,7 @@ public class SurfaceTextureRenderer {
}
checkGlError("before updateTexImage");
mSurfaceTexture.updateTexImage();
+ if (targetSurfaces == null) return;
for (EGLSurfaceHolder holder : mSurfaces) {
if (targetSurfaces.contains(holder.surface)) {
makeCurrent(holder.eglSurface);
diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
index 3036425..fff171b 100644
--- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
+++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
@@ -63,6 +63,12 @@ import static com.android.internal.util.Preconditions.*;
public final class StreamConfigurationMap {
private static final String TAG = "StreamConfigurationMap";
+
+ /**
+ * Indicates that a minimum frame duration is not available for a particular configuration.
+ */
+ public static final long NO_MIN_FRAME_DURATION = 0;
+
/**
* Create a new {@link StreamConfigurationMap}.
*
@@ -359,7 +365,9 @@ public final class StreamConfigurationMap {
*
* @param format an image format from {@link ImageFormat} or {@link PixelFormat}
* @param size an output-compatible size
- * @return a minimum frame duration {@code >=} 0 in nanoseconds
+ * @return a minimum frame duration {@code >} 0 in nanoseconds, or
+ * {@link #NO_MIN_FRAME_DURATION} if the minimum frame duration is not available (this
+ * can only occur on limited mode devices).
*
* @throws IllegalArgumentException if {@code format} or {@code size} was not supported
* @throws NullPointerException if {@code size} was {@code null}
@@ -406,7 +414,9 @@ public final class StreamConfigurationMap {
* a class which is supported by {@link #isOutputSupportedFor(Class)} and has a
* non-empty array returned by {@link #getOutputSizes(Class)}
* @param size an output-compatible size
- * @return a minimum frame duration {@code >=} 0 in nanoseconds
+ * @return a minimum frame duration {@code >} 0 in nanoseconds, or
+ * {@link #NO_MIN_FRAME_DURATION} if the minimum frame duration is not available (this
+ * can only occur on limited mode devices).
*
* @throws IllegalArgumentException if {@code klass} or {@code size} was not supported
* @throws NullPointerException if {@code size} or {@code klass} was {@code null}
@@ -892,7 +902,7 @@ public final class StreamConfigurationMap {
private long getDurationDefault(int duration) {
switch (duration) {
case DURATION_MIN_FRAME:
- throw new AssertionError("Minimum frame durations are required to be listed");
+ return NO_MIN_FRAME_DURATION;
case DURATION_STALL:
return 0L; // OK. A lack of a stall duration implies a 0 stall duration
default:
diff --git a/core/java/android/hardware/camera2/utils/CloseableLock.java b/core/java/android/hardware/camera2/utils/CloseableLock.java
new file mode 100644
index 0000000..af55055
--- /dev/null
+++ b/core/java/android/hardware/camera2/utils/CloseableLock.java
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.camera2.utils;
+
+import android.util.Log;
+
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Implement a shared/exclusive lock that can be closed.
+ *
+ * <p>A shared lock can be acquired if any other shared locks are also acquired. An
+ * exclusive lock acquire will block until all shared locks have been released.</p>
+ *
+ * <p>Locks are re-entrant; trying to acquire another lock (of the same type)
+ * while a lock is already held will immediately succeed.</p>
+ *
+ * <p>Acquiring to acquire a shared lock while holding an exclusive lock or vice versa is not
+ * supported; attempting it will throw an {@link IllegalStateException}.</p>
+ *
+ * <p>If the lock is closed, all future and current acquires will immediately return {@code null}.
+ * </p>
+ */
+public class CloseableLock implements AutoCloseable {
+
+ private static final boolean VERBOSE = false;
+
+ private final String TAG = "CloseableLock";
+ private final String mName;
+
+ private volatile boolean mClosed = false;
+
+ /** If an exclusive lock is acquired by some thread. */
+ private boolean mExclusive = false;
+ /**
+ * How many shared locks are acquired by any thread:
+ *
+ * <p>Reentrant locking increments this. If an exclusive lock is held,
+ * this value will stay at 0.</p>
+ */
+ private int mSharedLocks = 0;
+
+ private final ReentrantLock mLock = new ReentrantLock();
+ /** This condition automatically releases mLock when waiting; re-acquiring it after notify */
+ private final Condition mCondition = mLock.newCondition();
+
+ /** How many times the current thread is holding the lock */
+ private final ThreadLocal<Integer> mLockCount =
+ new ThreadLocal<Integer>() {
+ @Override protected Integer initialValue() {
+ return 0;
+ }
+ };
+
+ /**
+ * Helper class to release a lock at the end of a try-with-resources statement.
+ */
+ public class ScopedLock implements AutoCloseable {
+ private ScopedLock() {}
+
+ /** Release the lock with {@link CloseableLock#releaseLock}. */
+ @Override
+ public void close() {
+ releaseLock();
+ }
+ }
+
+ /**
+ * Create a new instance; starts out with 0 locks acquired.
+ */
+ public CloseableLock() {
+ mName = "";
+ }
+
+ /**
+ * Create a new instance; starts out with 0 locks acquired.
+ *
+ * @param name set an optional name for logging functionality
+ */
+ public CloseableLock(String name) {
+ mName = name;
+ }
+
+ /**
+ * Acquires the lock exclusively (blocking), marks it as closed, then releases the lock.
+ *
+ * <p>Marking a lock as closed will fail all further acquisition attempts;
+ * it will also immediately unblock all other threads currently trying to acquire a lock.</p>
+ *
+ * <p>This operation is idempotent; calling it more than once has no effect.</p>
+ *
+ * @throws IllegalStateException
+ * if an attempt is made to {@code close} while this thread has a lock acquired
+ */
+ @Override
+ public void close() {
+ if (mClosed) {
+ log("close - already closed; ignoring");
+ return;
+ }
+
+ ScopedLock scoper = acquireExclusiveLock();
+ // Already closed by another thread?
+ if (scoper == null) {
+ return;
+ } else if (mLockCount.get() != 1) {
+ // Future: may want to add a #releaseAndClose to allow this.
+ throw new IllegalStateException(
+ "Cannot close while one or more acquired locks are being held by this " +
+ "thread; release all other locks first");
+ }
+
+ try {
+ mLock.lock();
+
+ mClosed = true;
+ mExclusive = false;
+ mSharedLocks = 0;
+ mLockCount.remove();
+
+ // Notify all threads that are waiting to unblock and return immediately
+ mCondition.signalAll();
+ } finally {
+ mLock.unlock();
+ }
+
+ log("close - completed");
+ }
+
+ /**
+ * Try to acquire the lock non-exclusively, blocking until the operation completes.
+ *
+ * <p>If the lock has already been closed, or being closed before this operation returns,
+ * the call will immediately return {@code false}.</p>
+ *
+ * <p>If other threads hold a non-exclusive lock (and the lock is not yet closed),
+ * this operation will return immediately. If another thread holds an exclusive lock,
+ * this thread will block until the exclusive lock has been released.</p>
+ *
+ * <p>This lock is re-entrant; acquiring more than one non-exclusive lock per thread is
+ * supported, and must be matched by an equal number of {@link #releaseLock} calls.</p>
+ *
+ * @return {@code ScopedLock} instance if the lock was acquired, or {@code null} if the lock
+ * was already closed.
+ *
+ * @throws IllegalStateException if this thread is already holding an exclusive lock
+ */
+ public ScopedLock acquireLock() {
+
+ int ownedLocks;
+
+ try {
+ mLock.lock();
+
+ // Lock is already closed, all further acquisitions will fail
+ if (mClosed) {
+ log("acquire lock early aborted (already closed)");
+ return null;
+ }
+
+ ownedLocks = mLockCount.get();
+
+ // This thread is already holding an exclusive lock
+ if (mExclusive && ownedLocks > 0) {
+ throw new IllegalStateException(
+ "Cannot acquire shared lock while holding exclusive lock");
+ }
+
+ // Is another thread holding the exclusive lock? Block until we can get in.
+ while (mExclusive) {
+ mCondition.awaitUninterruptibly();
+
+ // Did another thread #close while we were waiting? Unblock immediately.
+ if (mClosed) {
+ log("acquire lock unblocked aborted (already closed)");
+ return null;
+ }
+ }
+
+ mSharedLocks++;
+
+ ownedLocks = mLockCount.get() + 1;
+ mLockCount.set(ownedLocks);
+ } finally {
+ mLock.unlock();
+ }
+
+ log("acquired lock (local own count = " + ownedLocks + "");
+ return new ScopedLock();
+ }
+
+ /**
+ * Try to acquire the lock exclusively, blocking until all other threads release their locks.
+ *
+ * <p>If the lock has already been closed, or being closed before this operation returns,
+ * the call will immediately return {@code false}.</p>
+ *
+ * <p>If any other threads are holding a lock, this thread will block until all
+ * other locks are released.</p>
+ *
+ * <p>This lock is re-entrant; acquiring more than one exclusive lock per thread is supported,
+ * and must be matched by an equal number of {@link #releaseLock} calls.</p>
+ *
+ * @return {@code ScopedLock} instance if the lock was acquired, or {@code null} if the lock
+ * was already closed.
+ *
+ * @throws IllegalStateException
+ * if an attempt is made to acquire an exclusive lock while already holding a lock
+ */
+ public ScopedLock acquireExclusiveLock() {
+
+ int ownedLocks;
+
+ try {
+ mLock.lock();
+
+ // Lock is already closed, all further acquisitions will fail
+ if (mClosed) {
+ log("acquire exclusive lock early aborted (already closed)");
+ return null;
+ }
+
+ ownedLocks = mLockCount.get();
+
+ // This thread is already holding a shared lock
+ if (!mExclusive && ownedLocks > 0) {
+ throw new IllegalStateException(
+ "Cannot acquire exclusive lock while holding shared lock");
+ }
+
+ /*
+ * Is another thread holding the lock? Block until we can get in.
+ *
+ * If we are already holding the lock, always let it through since
+ * we are just reentering the exclusive lock.
+ */
+ while (ownedLocks == 0 && (mExclusive || mSharedLocks > 0)) {
+ mCondition.awaitUninterruptibly();
+
+ // Did another thread #close while we were waiting? Unblock immediately.
+ if (mClosed) {
+ log("acquire exclusive lock unblocked aborted (already closed)");
+ return null;
+ }
+ }
+
+ mExclusive = true;
+
+ ownedLocks = mLockCount.get() + 1;
+ mLockCount.set(ownedLocks);
+ } finally {
+ mLock.unlock();
+ }
+
+ log("acquired exclusive lock (local own count = " + ownedLocks + "");
+ return new ScopedLock();
+ }
+
+ /**
+ * Release a single lock that was acquired.
+ *
+ * <p>Any other other that is blocked and trying to acquire a lock will get a chance
+ * to acquire the lock.</p>
+ *
+ * @throws IllegalStateException if no locks were acquired, or if the lock was already closed
+ */
+ public void releaseLock() {
+ if (mLockCount.get() <= 0) {
+ throw new IllegalStateException(
+ "Cannot release lock that was not acquired by this thread");
+ }
+
+ int ownedLocks;
+
+ try {
+ mLock.lock();
+
+ // Lock is already closed, it couldn't have been acquired in the first place
+ if (mClosed) {
+ throw new IllegalStateException("Do not release after the lock has been closed");
+ }
+
+ if (!mExclusive) {
+ mSharedLocks--;
+ } else {
+ if (mSharedLocks != 0) {
+ throw new AssertionError("Too many shared locks " + mSharedLocks);
+ }
+ }
+
+ ownedLocks = mLockCount.get() - 1;
+ mLockCount.set(ownedLocks);
+
+ if (ownedLocks == 0 && mExclusive) {
+ // Wake up any threads that might be waiting for the exclusive lock to be released
+ mExclusive = false;
+ mCondition.signalAll();
+ } else if (ownedLocks == 0 && mSharedLocks == 0) {
+ // Wake up any threads that might be trying to get the exclusive lock
+ mCondition.signalAll();
+ }
+ } finally {
+ mLock.unlock();
+ }
+
+ log("released lock (local lock count " + ownedLocks + ")");
+ }
+
+ private void log(String what) {
+ if (VERBOSE) {
+ Log.v(TAG + "[" + mName + "]", what);
+ }
+ }
+
+}
diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl
index 9218c11..53e87a6 100644
--- a/core/java/android/nfc/INfcAdapter.aidl
+++ b/core/java/android/nfc/INfcAdapter.aidl
@@ -25,6 +25,7 @@ import android.nfc.IAppCallback;
import android.nfc.INfcAdapterExtras;
import android.nfc.INfcTag;
import android.nfc.INfcCardEmulation;
+import android.nfc.INfcLockscreenDispatch;
import android.os.Bundle;
/**
@@ -52,4 +53,6 @@ interface INfcAdapter
void setReaderMode (IBinder b, IAppCallback callback, int flags, in Bundle extras);
void setP2pModes(int initatorModes, int targetModes);
+
+ void registerLockscreenDispatch(INfcLockscreenDispatch lockscreenDispatch, in int[] techList);
}
diff --git a/core/java/android/nfc/INfcLockscreenDispatch.aidl b/core/java/android/nfc/INfcLockscreenDispatch.aidl
new file mode 100644
index 0000000..27e9a8c
--- /dev/null
+++ b/core/java/android/nfc/INfcLockscreenDispatch.aidl
@@ -0,0 +1,12 @@
+package android.nfc;
+
+import android.nfc.Tag;
+
+/**
+ * @hide
+ */
+interface INfcLockscreenDispatch {
+
+ boolean onTagDetected(in Tag tag);
+
+}
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index dd8e41c..8991066 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -380,6 +380,16 @@ public final class NfcAdapter {
public Uri[] createBeamUris(NfcEvent event);
}
+
+ /**
+ * A callback to be invoked when an application has registered for receiving
+ * tags at the lockscreen.
+ */
+ public interface NfcLockscreenDispatch {
+ public boolean onTagDetected(Tag tag);
+ }
+
+
/**
* Helper to check if this device has FEATURE_NFC, but without using
* a context.
@@ -1417,6 +1427,23 @@ public final class NfcAdapter {
}
}
+ public boolean registerLockscreenDispatch(final NfcLockscreenDispatch lockscreenDispatch,
+ int[] techList) {
+ try {
+ sService.registerLockscreenDispatch(new INfcLockscreenDispatch.Stub() {
+ @Override
+ public boolean onTagDetected(Tag tag) throws RemoteException {
+ return lockscreenDispatch.onTagDetected(tag);
+ }
+ }, techList);
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ return false;
+ }
+
+ return true;
+ }
+
/**
* @hide
*/
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 1e7d7f1..8038a38 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -1694,7 +1694,7 @@ public final class ContactsContract {
*/
public static final class Entity implements BaseColumns, ContactsColumns,
ContactNameColumns, RawContactsColumns, BaseSyncColumns, SyncColumns, DataColumns,
- StatusColumns, ContactOptionsColumns, ContactStatusColumns {
+ StatusColumns, ContactOptionsColumns, ContactStatusColumns, DataUsageStatColumns {
/**
* no public constructor since this is a utility class
*/
@@ -7813,6 +7813,25 @@ public final class ContactsContract {
"pinned_position_update");
/**
+ * <p>
+ * The method to invoke in order to undemote a formerly demoted contact. The contact id of
+ * the contact must be provided as an argument. If the contact was not previously demoted,
+ * nothing will be done.
+ * </p>
+ *
+ * <p>
+ * Example:
+ * <pre>
+ * final long contactId = 10;
+ * resolver.call(ContactsContract.AUTHORITY_URI, PinnedPositions.UNDEMOTE_METHOD,
+ * String.valueOf(contactId), null);
+ * </pre>
+ *
+ * </p>
+ */
+ public static final String UNDEMOTE_METHOD = "undemote";
+
+ /**
* Default value for the pinned position of an unpinned contact. Also equal to
* {@link Integer#MAX_VALUE}.
*/
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 06c05ee..2a5e9fd 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4537,12 +4537,6 @@ public final class Settings {
public static final String SMS_DEFAULT_APPLICATION = "sms_default_application";
/**
- * Specifies the package name currently configured to be the primary phone application
- * @hide
- */
- public static final String PHONE_DEFAULT_APPLICATION = "phone_default_application";
-
- /**
* Name of a package that the current user has explicitly allowed to see all of that
* user's notifications.
*
diff --git a/core/java/android/util/PathParser.java b/core/java/android/util/PathParser.java
new file mode 100644
index 0000000..f90ce51
--- /dev/null
+++ b/core/java/android/util/PathParser.java
@@ -0,0 +1,528 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.util;
+
+import android.graphics.Path;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * @hide
+ */
+public class PathParser {
+ static final String LOGTAG = PathParser.class.getSimpleName();
+
+ /**
+ * @param pathData The string representing a path, the same as "d" string in svg file.
+ * @return the generated Path object.
+ */
+ public static Path createPathFromPathData(String pathData) {
+ Path path = new Path();
+ PathDataNode[] nodes = createNodesFromPathData(pathData);
+ if (nodes != null) {
+ PathDataNode.nodesToPath(nodes, path);
+ return path;
+ }
+ return null;
+ }
+
+ /**
+ * @param pathData The string representing a path, the same as "d" string in svg file.
+ * @return an array of the PathDataNode.
+ */
+ public static PathDataNode[] createNodesFromPathData(String pathData) {
+ int start = 0;
+ int end = 1;
+
+ ArrayList<PathDataNode> list = new ArrayList<PathDataNode>();
+ while (end < pathData.length()) {
+ end = nextStart(pathData, end);
+ String s = pathData.substring(start, end);
+ float[] val = getFloats(s);
+ addNode(list, s.charAt(0), val);
+
+ start = end;
+ end++;
+ }
+ if ((end - start) == 1 && start < pathData.length()) {
+ addNode(list, pathData.charAt(start), new float[0]);
+ }
+ return list.toArray(new PathDataNode[list.size()]);
+ }
+
+ private static int nextStart(String s, int end) {
+ char c;
+
+ while (end < s.length()) {
+ c = s.charAt(end);
+ if (((c - 'A') * (c - 'Z') <= 0) || (((c - 'a') * (c - 'z') <= 0))) {
+ return end;
+ }
+ end++;
+ }
+ return end;
+ }
+
+ private static void addNode(ArrayList<PathDataNode> list, char cmd, float[] val) {
+ list.add(new PathDataNode(cmd, val));
+ }
+
+
+ /**
+ * Parse the floats in the string.
+ * This is an optimized version of parseFloat(s.split(",|\\s"));
+ *
+ * @param s the string containing a command and list of floats
+ * @return array of floats
+ */
+ private static float[] getFloats(String s) {
+ if (s.charAt(0) == 'z' | s.charAt(0) == 'Z') {
+ return new float[0];
+ }
+ try {
+ float[] tmp = new float[s.length()];
+ int count = 0;
+ int pos = 1, end;
+ while ((end = extract(s, pos)) >= 0) {
+ if (pos < end) {
+ tmp[count++] = Float.parseFloat(s.substring(pos, end));
+ }
+ pos = end + 1;
+ }
+ // handle the final float if there is one
+ if (pos < s.length()) {
+ tmp[count++] = Float.parseFloat(s.substring(pos, s.length()));
+ }
+ return Arrays.copyOf(tmp, count);
+ } catch (NumberFormatException e){
+ Log.e(LOGTAG,"error in parsing \""+s+"\"");
+ throw e;
+ }
+ }
+
+ /**
+ * Calculate the position of the next comma or space
+ * @param s the string to search
+ * @param start the position to start searching
+ * @return the position of the next comma or space or -1 if none found
+ */
+ private static int extract(String s, int start) {
+ int space = s.indexOf(' ', start);
+ int comma = s.indexOf(',', start);
+ if (space == -1) {
+ return comma;
+ }
+ if (comma == -1) {
+ return space;
+ }
+ return (comma > space) ? space : comma;
+ }
+
+ public static class PathDataNode {
+ private char mType;
+ private float[] mParams;
+
+ private PathDataNode(char type, float[] params) {
+ mType = type;
+ mParams = params;
+ }
+
+ private PathDataNode(PathDataNode n) {
+ mType = n.mType;
+ mParams = Arrays.copyOf(n.mParams, n.mParams.length);
+ }
+
+ public static void nodesToPath(PathDataNode[] node, Path path) {
+ float[] current = new float[4];
+ char previousCommand = 'm';
+ for (int i = 0; i < node.length; i++) {
+ addCommand(path, current, previousCommand, node[i].mType, node[i].mParams);
+ previousCommand = node[i].mType;
+ }
+ }
+
+ private static void addCommand(Path path, float[] current,
+ char previousCmd, char cmd, float[] val) {
+
+ int incr = 2;
+ float currentX = current[0];
+ float currentY = current[1];
+ float ctrlPointX = current[2];
+ float ctrlPointY = current[3];
+ float reflectiveCtrlPointX;
+ float reflectiveCtrlPointY;
+
+ switch (cmd) {
+ case 'z':
+ case 'Z':
+ path.close();
+ return;
+ case 'm':
+ case 'M':
+ case 'l':
+ case 'L':
+ case 't':
+ case 'T':
+ incr = 2;
+ break;
+ case 'h':
+ case 'H':
+ case 'v':
+ case 'V':
+ incr = 1;
+ break;
+ case 'c':
+ case 'C':
+ incr = 6;
+ break;
+ case 's':
+ case 'S':
+ case 'q':
+ case 'Q':
+ incr = 4;
+ break;
+ case 'a':
+ case 'A':
+ incr = 7;
+ break;
+ }
+ for (int k = 0; k < val.length; k += incr) {
+ switch (cmd) {
+ case 'm': // moveto - Start a new sub-path (relative)
+ path.rMoveTo(val[k + 0], val[k + 1]);
+ currentX += val[k + 0];
+ currentY += val[k + 1];
+ break;
+ case 'M': // moveto - Start a new sub-path
+ path.moveTo(val[k + 0], val[k + 1]);
+ currentX = val[k + 0];
+ currentY = val[k + 1];
+ break;
+ case 'l': // lineto - Draw a line from the current point (relative)
+ path.rLineTo(val[k + 0], val[k + 1]);
+ currentX += val[k + 0];
+ currentY += val[k + 1];
+ break;
+ case 'L': // lineto - Draw a line from the current point
+ path.lineTo(val[k + 0], val[k + 1]);
+ currentX = val[k + 0];
+ currentY = val[k + 1];
+ break;
+ case 'z': // closepath - Close the current subpath
+ case 'Z': // closepath - Close the current subpath
+ path.close();
+ break;
+ case 'h': // horizontal lineto - Draws a horizontal line (relative)
+ path.rLineTo(val[k + 0], 0);
+ currentX += val[k + 0];
+ break;
+ case 'H': // horizontal lineto - Draws a horizontal line
+ path.lineTo(val[k + 0], currentY);
+ currentX = val[k + 0];
+ break;
+ case 'v': // vertical lineto - Draws a vertical line from the current point (r)
+ path.rLineTo(0, val[k + 0]);
+ currentY += val[k + 0];
+ break;
+ case 'V': // vertical lineto - Draws a vertical line from the current point
+ path.lineTo(currentX, val[k + 0]);
+ currentY = val[k + 0];
+ break;
+ case 'c': // curveto - Draws a cubic Bézier curve (relative)
+ path.rCubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3],
+ val[k + 4], val[k + 5]);
+
+ ctrlPointX = currentX + val[k + 2];
+ ctrlPointY = currentY + val[k + 3];
+ currentX += val[k + 4];
+ currentY += val[k + 5];
+
+ break;
+ case 'C': // curveto - Draws a cubic Bézier curve
+ path.cubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3],
+ val[k + 4], val[k + 5]);
+ currentX = val[k + 4];
+ currentY = val[k + 5];
+ ctrlPointX = val[k + 2];
+ ctrlPointY = val[k + 3];
+ break;
+ case 's': // smooth curveto - Draws a cubic Bézier curve (reflective cp)
+ reflectiveCtrlPointX = 0;
+ reflectiveCtrlPointY = 0;
+ if (previousCmd == 'c' || previousCmd == 's'
+ || previousCmd == 'C' || previousCmd == 'S') {
+ reflectiveCtrlPointX = currentX - ctrlPointX;
+ reflectiveCtrlPointY = currentY - ctrlPointY;
+ }
+ path.rCubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
+ val[k + 0], val[k + 1],
+ val[k + 2], val[k + 3]);
+
+ ctrlPointX = currentX + val[k + 0];
+ ctrlPointY = currentY + val[k + 1];
+ currentX += val[k + 2];
+ currentY += val[k + 3];
+ break;
+ case 'S': // shorthand/smooth curveto Draws a cubic Bézier curve(reflective cp)
+ reflectiveCtrlPointX = currentX;
+ reflectiveCtrlPointY = currentY;
+ if (previousCmd == 'c' || previousCmd == 's'
+ || previousCmd == 'C' || previousCmd == 'S') {
+ reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
+ reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
+ }
+ path.cubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
+ val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
+ ctrlPointX = val[k + 0];
+ ctrlPointY = val[k + 1];
+ currentX = val[k + 2];
+ currentY = val[k + 3];
+ break;
+ case 'q': // Draws a quadratic Bézier (relative)
+ path.rQuadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
+ ctrlPointX = currentX + val[k + 0];
+ ctrlPointY = currentY + val[k + 1];
+ currentX += val[k + 2];
+ currentY += val[k + 3];
+ break;
+ case 'Q': // Draws a quadratic Bézier
+ path.quadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
+ ctrlPointX = val[k + 0];
+ ctrlPointY = val[k + 1];
+ currentX = val[k + 2];
+ currentY = val[k + 3];
+ break;
+ case 't': // Draws a quadratic Bézier curve(reflective control point)(relative)
+ reflectiveCtrlPointX = 0;
+ reflectiveCtrlPointY = 0;
+ if (previousCmd == 'q' || previousCmd == 't'
+ || previousCmd == 'Q' || previousCmd == 'T') {
+ reflectiveCtrlPointX = currentX - ctrlPointX;
+ reflectiveCtrlPointY = currentY - ctrlPointY;
+ }
+ path.rQuadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
+ val[k + 0], val[k + 1]);
+ ctrlPointX = currentX + reflectiveCtrlPointX;
+ ctrlPointY = currentY + reflectiveCtrlPointY;
+ currentX += val[k + 0];
+ currentY += val[k + 1];
+ break;
+ case 'T': // Draws a quadratic Bézier curve (reflective control point)
+ reflectiveCtrlPointX = currentX;
+ reflectiveCtrlPointY = currentY;
+ if (previousCmd == 'q' || previousCmd == 't'
+ || previousCmd == 'Q' || previousCmd == 'T') {
+ reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
+ reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
+ }
+ path.quadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
+ val[k + 0], val[k + 1]);
+ ctrlPointX = reflectiveCtrlPointX;
+ ctrlPointY = reflectiveCtrlPointY;
+ currentX = val[k + 0];
+ currentY = val[k + 1];
+ break;
+ case 'a': // Draws an elliptical arc
+ // (rx ry x-axis-rotation large-arc-flag sweep-flag x y)
+ drawArc(path,
+ currentX,
+ currentY,
+ val[k + 5] + currentX,
+ val[k + 6] + currentY,
+ val[k + 0],
+ val[k + 1],
+ val[k + 2],
+ val[k + 3] != 0,
+ val[k + 4] != 0);
+ currentX += val[k + 5];
+ currentY += val[k + 6];
+ ctrlPointX = currentX;
+ ctrlPointY = currentY;
+ break;
+ case 'A': // Draws an elliptical arc
+ drawArc(path,
+ currentX,
+ currentY,
+ val[k + 5],
+ val[k + 6],
+ val[k + 0],
+ val[k + 1],
+ val[k + 2],
+ val[k + 3] != 0,
+ val[k + 4] != 0);
+ currentX = val[k + 5];
+ currentY = val[k + 6];
+ ctrlPointX = currentX;
+ ctrlPointY = currentY;
+ break;
+ }
+ previousCmd = cmd;
+ }
+ current[0] = currentX;
+ current[1] = currentY;
+ current[2] = ctrlPointX;
+ current[3] = ctrlPointY;
+ }
+
+ private static void drawArc(Path p,
+ float x0,
+ float y0,
+ float x1,
+ float y1,
+ float a,
+ float b,
+ float theta,
+ boolean isMoreThanHalf,
+ boolean isPositiveArc) {
+
+ /* Convert rotation angle from degrees to radians */
+ double thetaD = Math.toRadians(theta);
+ /* Pre-compute rotation matrix entries */
+ double cosTheta = Math.cos(thetaD);
+ double sinTheta = Math.sin(thetaD);
+ /* Transform (x0, y0) and (x1, y1) into unit space */
+ /* using (inverse) rotation, followed by (inverse) scale */
+ double x0p = (x0 * cosTheta + y0 * sinTheta) / a;
+ double y0p = (-x0 * sinTheta + y0 * cosTheta) / b;
+ double x1p = (x1 * cosTheta + y1 * sinTheta) / a;
+ double y1p = (-x1 * sinTheta + y1 * cosTheta) / b;
+
+ /* Compute differences and averages */
+ double dx = x0p - x1p;
+ double dy = y0p - y1p;
+ double xm = (x0p + x1p) / 2;
+ double ym = (y0p + y1p) / 2;
+ /* Solve for intersecting unit circles */
+ double dsq = dx * dx + dy * dy;
+ if (dsq == 0.0) {
+ Log.w(LOGTAG, " Points are coincident");
+ return; /* Points are coincident */
+ }
+ double disc = 1.0 / dsq - 1.0 / 4.0;
+ if (disc < 0.0) {
+ Log.w(LOGTAG, "Points are too far apart " + dsq);
+ float adjust = (float) (Math.sqrt(dsq) / 1.99999);
+ drawArc(p, x0, y0, x1, y1, a * adjust,
+ b * adjust, theta, isMoreThanHalf, isPositiveArc);
+ return; /* Points are too far apart */
+ }
+ double s = Math.sqrt(disc);
+ double sdx = s * dx;
+ double sdy = s * dy;
+ double cx;
+ double cy;
+ if (isMoreThanHalf == isPositiveArc) {
+ cx = xm - sdy;
+ cy = ym + sdx;
+ } else {
+ cx = xm + sdy;
+ cy = ym - sdx;
+ }
+
+ double eta0 = Math.atan2((y0p - cy), (x0p - cx));
+
+ double eta1 = Math.atan2((y1p - cy), (x1p - cx));
+
+ double sweep = (eta1 - eta0);
+ if (isPositiveArc != (sweep >= 0)) {
+ if (sweep > 0) {
+ sweep -= 2 * Math.PI;
+ } else {
+ sweep += 2 * Math.PI;
+ }
+ }
+
+ cx *= a;
+ cy *= b;
+ double tcx = cx;
+ cx = cx * cosTheta - cy * sinTheta;
+ cy = tcx * sinTheta + cy * cosTheta;
+
+ arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep);
+ }
+
+ /**
+ * Converts an arc to cubic Bezier segments and records them in p.
+ *
+ * @param p The target for the cubic Bezier segments
+ * @param cx The x coordinate center of the ellipse
+ * @param cy The y coordinate center of the ellipse
+ * @param a The radius of the ellipse in the horizontal direction
+ * @param b The radius of the ellipse in the vertical direction
+ * @param e1x E(eta1) x coordinate of the starting point of the arc
+ * @param e1y E(eta2) y coordinate of the starting point of the arc
+ * @param theta The angle that the ellipse bounding rectangle makes with horizontal plane
+ * @param start The start angle of the arc on the ellipse
+ * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse
+ */
+ private static void arcToBezier(Path p,
+ double cx,
+ double cy,
+ double a,
+ double b,
+ double e1x,
+ double e1y,
+ double theta,
+ double start,
+ double sweep) {
+ // Taken from equations at: http://spaceroots.org/documents/ellipse/node8.html
+ // and http://www.spaceroots.org/documents/ellipse/node22.html
+
+ // Maximum of 45 degrees per cubic Bezier segment
+ int numSegments = Math.abs((int) Math.ceil(sweep * 4 / Math.PI));
+
+ double eta1 = start;
+ double cosTheta = Math.cos(theta);
+ double sinTheta = Math.sin(theta);
+ double cosEta1 = Math.cos(eta1);
+ double sinEta1 = Math.sin(eta1);
+ double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1);
+ double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1);
+
+ double anglePerSegment = sweep / numSegments;
+ for (int i = 0; i < numSegments; i++) {
+ double eta2 = eta1 + anglePerSegment;
+ double sinEta2 = Math.sin(eta2);
+ double cosEta2 = Math.cos(eta2);
+ double e2x = cx + (a * cosTheta * cosEta2) - (b * sinTheta * sinEta2);
+ double e2y = cy + (a * sinTheta * cosEta2) + (b * cosTheta * sinEta2);
+ double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2;
+ double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2;
+ double tanDiff2 = Math.tan((eta2 - eta1) / 2);
+ double alpha =
+ Math.sin(eta2 - eta1) * (Math.sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3;
+ double q1x = e1x + alpha * ep1x;
+ double q1y = e1y + alpha * ep1y;
+ double q2x = e2x - alpha * ep2x;
+ double q2y = e2y - alpha * ep2y;
+
+ p.cubicTo((float) q1x,
+ (float) q1y,
+ (float) q2x,
+ (float) q2y,
+ (float) e2x,
+ (float) e2y);
+ eta1 = eta2;
+ e1x = e2x;
+ e1y = e2y;
+ ep1x = ep2x;
+ ep1y = ep2y;
+ }
+ }
+
+ }
+}
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index 68e2146..9fafc48 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -853,31 +853,7 @@ class GLES20Canvas extends HardwareCanvas {
private static native void nDrawPoints(long renderer, float[] points,
int offset, int count, long paint);
- @SuppressWarnings("deprecation")
- @Override
- public void drawPosText(char[] text, int index, int count, float[] pos, Paint paint) {
- if (index < 0 || index + count > text.length || count * 2 > pos.length) {
- throw new IndexOutOfBoundsException();
- }
-
- nDrawPosText(mRenderer, text, index, count, pos, paint.mNativePaint);
- }
-
- private static native void nDrawPosText(long renderer, char[] text, int index, int count,
- float[] pos, long paint);
-
- @SuppressWarnings("deprecation")
- @Override
- public void drawPosText(String text, float[] pos, Paint paint) {
- if (text.length() * 2 > pos.length) {
- throw new ArrayIndexOutOfBoundsException();
- }
-
- nDrawPosText(mRenderer, text, 0, text.length(), pos, paint.mNativePaint);
- }
-
- private static native void nDrawPosText(long renderer, String text, int start, int end,
- float[] pos, long paint);
+ // Note: drawPosText just uses implementation in Canvas
@Override
public void drawRect(float left, float top, float right, float bottom, Paint paint) {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 89c2f37..d25ef16 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -14240,6 +14240,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @return True if the view is attached to a window and the window is
* hardware accelerated; false in any other case.
*/
+ @ViewDebug.ExportedProperty(category = "drawing")
public boolean isHardwareAccelerated() {
return mAttachInfo != null && mAttachInfo.mHardwareAccelerated;
}
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index a02e76b..1e72625 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -4386,7 +4386,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
// Make sure we do not set both flags at the same time
int opaqueFlag = isOpaque ? PFLAG_DIRTY_OPAQUE : PFLAG_DIRTY;
- if (child.mLayerType == LAYER_TYPE_SOFTWARE) {
+ if (child.mLayerType != LAYER_TYPE_NONE) {
mPrivateFlags |= PFLAG_INVALIDATED;
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
}
@@ -4497,7 +4497,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
location[CHILD_LEFT_INDEX] = left;
location[CHILD_TOP_INDEX] = top;
- if (mLayerType == LAYER_TYPE_SOFTWARE) {
+ if (mLayerType != LAYER_TYPE_NONE) {
mPrivateFlags |= PFLAG_INVALIDATED;
}
@@ -4515,7 +4515,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
}
- if (mLayerType == LAYER_TYPE_SOFTWARE) {
+ if (mLayerType != LAYER_TYPE_NONE) {
mPrivateFlags |= PFLAG_INVALIDATED;
}
diff --git a/core/java/android/view/animation/PathInterpolator.java b/core/java/android/view/animation/PathInterpolator.java
index da12ffb..945ecf0 100644
--- a/core/java/android/view/animation/PathInterpolator.java
+++ b/core/java/android/view/animation/PathInterpolator.java
@@ -21,6 +21,7 @@ import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import android.graphics.Path;
import android.util.AttributeSet;
+import android.util.PathParser;
import android.view.InflateException;
import com.android.internal.R;
@@ -102,28 +103,40 @@ public class PathInterpolator implements Interpolator {
}
private void parseInterpolatorFromTypeArray(TypedArray a) {
- if (!a.hasValue(R.styleable.PathInterpolator_controlX1)) {
- throw new InflateException("pathInterpolator requires the controlX1 attribute");
- } else if (!a.hasValue(R.styleable.PathInterpolator_controlY1)) {
- throw new InflateException("pathInterpolator requires the controlY1 attribute");
- }
- float x1 = a.getFloat(R.styleable.PathInterpolator_controlX1, 0);
- float y1 = a.getFloat(R.styleable.PathInterpolator_controlY1, 0);
+ // If there is pathData defined in the xml file, then the controls points
+ // will be all coming from pathData.
+ if (a.hasValue(R.styleable.PathInterpolator_pathData)) {
+ String pathData = a.getString(R.styleable.PathInterpolator_pathData);
+ Path path = PathParser.createPathFromPathData(pathData);
+ if (path == null) {
+ throw new InflateException("The path is null, which is created"
+ + " from " + pathData);
+ }
+ initPath(path);
+ } else {
+ if (!a.hasValue(R.styleable.PathInterpolator_controlX1)) {
+ throw new InflateException("pathInterpolator requires the controlX1 attribute");
+ } else if (!a.hasValue(R.styleable.PathInterpolator_controlY1)) {
+ throw new InflateException("pathInterpolator requires the controlY1 attribute");
+ }
+ float x1 = a.getFloat(R.styleable.PathInterpolator_controlX1, 0);
+ float y1 = a.getFloat(R.styleable.PathInterpolator_controlY1, 0);
- boolean hasX2 = a.hasValue(R.styleable.PathInterpolator_controlX2);
- boolean hasY2 = a.hasValue(R.styleable.PathInterpolator_controlY2);
+ boolean hasX2 = a.hasValue(R.styleable.PathInterpolator_controlX2);
+ boolean hasY2 = a.hasValue(R.styleable.PathInterpolator_controlY2);
- if (hasX2 != hasY2) {
- throw new InflateException(
- "pathInterpolator requires both controlX2 and controlY2 for cubic Beziers.");
- }
+ if (hasX2 != hasY2) {
+ throw new InflateException(
+ "pathInterpolator requires both controlX2 and controlY2 for cubic Beziers.");
+ }
- if (!hasX2) {
- initQuad(x1, y1);
- } else {
- float x2 = a.getFloat(R.styleable.PathInterpolator_controlX2, 0);
- float y2 = a.getFloat(R.styleable.PathInterpolator_controlY2, 0);
- initCubic(x1, y1, x2, y2);
+ if (!hasX2) {
+ initQuad(x1, y1);
+ } else {
+ float x2 = a.getFloat(R.styleable.PathInterpolator_controlX2, 0);
+ float y2 = a.getFloat(R.styleable.PathInterpolator_controlY2, 0);
+ initCubic(x1, y1, x2, y2);
+ }
}
}
@@ -216,5 +229,4 @@ public class PathInterpolator implements Interpolator {
float endY = mY[endIndex];
return startY + (fraction * (endY - startY));
}
-
}
diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java
index 04b18c1..93810b3 100644
--- a/core/java/android/widget/GridView.java
+++ b/core/java/android/widget/GridView.java
@@ -1326,12 +1326,28 @@ public class GridView extends AbsListView {
if (sel != null) {
positionSelector(INVALID_POSITION, sel);
mSelectedTop = sel.getTop();
- } else if (mTouchMode > TOUCH_MODE_DOWN && mTouchMode < TOUCH_MODE_SCROLL) {
- View child = getChildAt(mMotionPosition - mFirstPosition);
- if (child != null) positionSelector(mMotionPosition, child);
} else {
- mSelectedTop = 0;
- mSelectorRect.setEmpty();
+ final boolean inTouchMode = mTouchMode > TOUCH_MODE_DOWN
+ && mTouchMode < TOUCH_MODE_SCROLL;
+ if (inTouchMode) {
+ // If the user's finger is down, select the motion position.
+ final View child = getChildAt(mMotionPosition - mFirstPosition);
+ if (child != null) {
+ positionSelector(mMotionPosition, child);
+ }
+ } else if (mSelectedPosition != INVALID_POSITION) {
+ // If we had previously positioned the selector somewhere,
+ // put it back there. It might not match up with the data,
+ // but it's transitioning out so it's not a big deal.
+ final View child = getChildAt(mSelectorPosition - mFirstPosition);
+ if (child != null) {
+ positionSelector(mSelectorPosition, child);
+ }
+ } else {
+ // Otherwise, clear selection.
+ mSelectedTop = 0;
+ mSelectorRect.setEmpty();
+ }
}
// Attempt to restore accessibility focus, if necessary.
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index eeb8015..1baeca8 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -1718,14 +1718,24 @@ public class ListView extends AbsListView {
}
mSelectedTop = sel.getTop();
} else {
- // If the user's finger is down, select the motion position.
- // Otherwise, clear selection.
- if (mTouchMode == TOUCH_MODE_TAP || mTouchMode == TOUCH_MODE_DONE_WAITING) {
+ final boolean inTouchMode = mTouchMode == TOUCH_MODE_TAP
+ || mTouchMode == TOUCH_MODE_DONE_WAITING;
+ if (inTouchMode) {
+ // If the user's finger is down, select the motion position.
final View child = getChildAt(mMotionPosition - mFirstPosition);
- if (child != null) {
+ if (child != null) {
positionSelector(mMotionPosition, child);
}
+ } else if (mSelectorPosition != INVALID_POSITION) {
+ // If we had previously positioned the selector somewhere,
+ // put it back there. It might not match up with the data,
+ // but it's transitioning out so it's not a big deal.
+ final View child = getChildAt(mSelectorPosition - mFirstPosition);
+ if (child != null) {
+ positionSelector(mSelectorPosition, child);
+ }
} else {
+ // Otherwise, clear selection.
mSelectedTop = 0;
mSelectorRect.setEmpty();
}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index f7d20b53..82637a1 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -1781,7 +1781,9 @@ public class RemoteViews implements Parcelable, Filter {
Parcel p = Parcel.obtain();
writeToParcel(p, 0);
p.setDataPosition(0);
- return new RemoteViews(p);
+ RemoteViews rv = new RemoteViews(p);
+ p.recycle();
+ return rv;
}
public String getPackage() {
diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java
index cca29cf..9a8380d 100644
--- a/core/java/android/widget/Switch.java
+++ b/core/java/android/widget/Switch.java
@@ -90,6 +90,7 @@ public class Switch extends CompoundButton {
private boolean mSplitTrack;
private CharSequence mTextOn;
private CharSequence mTextOff;
+ private boolean mShowText;
private int mTouchMode;
private int mTouchSlop;
@@ -188,6 +189,7 @@ public class Switch extends CompoundButton {
mTrackDrawable = a.getDrawable(com.android.internal.R.styleable.Switch_track);
mTextOn = a.getText(com.android.internal.R.styleable.Switch_textOn);
mTextOff = a.getText(com.android.internal.R.styleable.Switch_textOff);
+ mShowText = a.getBoolean(com.android.internal.R.styleable.Switch_showText, true);
mThumbTextPadding = a.getDimensionPixelSize(
com.android.internal.R.styleable.Switch_thumbTextPadding, 0);
mSwitchMinWidth = a.getDimensionPixelSize(
@@ -533,20 +535,43 @@ public class Switch extends CompoundButton {
requestLayout();
}
+ /**
+ * Sets whether the on/off text should be displayed.
+ *
+ * @param showText {@code true} to display on/off text
+ * @hide
+ */
+ public void setShowText(boolean showText) {
+ if (mShowText != showText) {
+ mShowText = showText;
+ requestLayout();
+ }
+ }
+
+ /**
+ * @return whether the on/off text should be displayed
+ * @hide
+ */
+ public boolean getShowText() {
+ return mShowText;
+ }
+
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- if (mOnLayout == null) {
- mOnLayout = makeLayout(mTextOn);
- }
+ if (mShowText) {
+ if (mOnLayout == null) {
+ mOnLayout = makeLayout(mTextOn);
+ }
- if (mOffLayout == null) {
- mOffLayout = makeLayout(mTextOff);
+ if (mOffLayout == null) {
+ mOffLayout = makeLayout(mTextOff);
+ }
}
mTrackDrawable.getPadding(mTempRect);
- final int maxTextWidth = Math.max(mOnLayout.getWidth(), mOffLayout.getWidth())
- + mThumbTextPadding * 2;
+ final int maxTextWidth = mShowText ? Math.max(mOnLayout.getWidth(), mOffLayout.getWidth())
+ + mThumbTextPadding * 2 : 0;
mThumbWidth = Math.max(maxTextWidth, mThumbDrawable.getIntrinsicWidth());
final int switchWidth = Math.max(mSwitchMinWidth,
@@ -568,9 +593,10 @@ public class Switch extends CompoundButton {
@Override
public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
super.onPopulateAccessibilityEvent(event);
- Layout layout = isChecked() ? mOnLayout : mOffLayout;
- if (layout != null && !TextUtils.isEmpty(layout.getText())) {
- event.getText().add(layout.getText());
+
+ final CharSequence text = isChecked() ? mTextOn : mTextOff;
+ if (text != null) {
+ event.getText().add(text);
}
}
diff --git a/core/java/com/android/internal/app/ProcessStats.java b/core/java/com/android/internal/app/ProcessStats.java
index 7e11850..4995ea1 100644
--- a/core/java/com/android/internal/app/ProcessStats.java
+++ b/core/java/com/android/internal/app/ProcessStats.java
@@ -28,7 +28,6 @@ import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
-import android.webkit.WebViewFactory;
import com.android.internal.util.GrowingArrayUtils;
@@ -55,6 +54,11 @@ public final class ProcessStats implements Parcelable {
// that is done.
public static long COMMIT_PERIOD = 3*60*60*1000; // Commit current stats every 3 hours
+ // Minimum uptime period before committing. If the COMMIT_PERIOD has elapsed but
+ // the total uptime has not exceeded this amount, then the commit will be held until
+ // it is reached.
+ public static long COMMIT_UPTIME_PERIOD = 60*60*1000; // Must have at least 1 hour elapsed
+
public static final int STATE_NOTHING = -1;
public static final int STATE_PERSISTENT = 0;
public static final int STATE_TOP = 1;
@@ -81,6 +85,24 @@ public final class ProcessStats implements Parcelable {
public static final int PSS_USS_MAXIMUM = 6;
public static final int PSS_COUNT = PSS_USS_MAXIMUM+1;
+ public static final int SYS_MEM_USAGE_SAMPLE_COUNT = 0;
+ public static final int SYS_MEM_USAGE_CACHED_MINIMUM = 1;
+ public static final int SYS_MEM_USAGE_CACHED_AVERAGE = 2;
+ public static final int SYS_MEM_USAGE_CACHED_MAXIMUM = 3;
+ public static final int SYS_MEM_USAGE_FREE_MINIMUM = 4;
+ public static final int SYS_MEM_USAGE_FREE_AVERAGE = 5;
+ public static final int SYS_MEM_USAGE_FREE_MAXIMUM = 6;
+ public static final int SYS_MEM_USAGE_ZRAM_MINIMUM = 7;
+ public static final int SYS_MEM_USAGE_ZRAM_AVERAGE = 8;
+ public static final int SYS_MEM_USAGE_ZRAM_MAXIMUM = 9;
+ public static final int SYS_MEM_USAGE_KERNEL_MINIMUM = 10;
+ public static final int SYS_MEM_USAGE_KERNEL_AVERAGE = 11;
+ public static final int SYS_MEM_USAGE_KERNEL_MAXIMUM = 12;
+ public static final int SYS_MEM_USAGE_NATIVE_MINIMUM = 13;
+ public static final int SYS_MEM_USAGE_NATIVE_AVERAGE = 14;
+ public static final int SYS_MEM_USAGE_NATIVE_MAXIMUM = 15;
+ public static final int SYS_MEM_USAGE_COUNT = SYS_MEM_USAGE_NATIVE_MAXIMUM+1;
+
public static final int ADJ_NOTHING = -1;
public static final int ADJ_MEM_FACTOR_NORMAL = 0;
public static final int ADJ_MEM_FACTOR_MODERATE = 1;
@@ -174,7 +196,7 @@ public final class ProcessStats implements Parcelable {
static final String CSV_SEP = "\t";
// Current version of the parcel format.
- private static final int PARCEL_VERSION = 14;
+ private static final int PARCEL_VERSION = 18;
// In-memory Parcel magic number, used to detect attempts to unmarshall bad data
private static final int MAGIC = 0x50535453;
@@ -200,11 +222,16 @@ public final class ProcessStats implements Parcelable {
public int mMemFactor = STATE_NOTHING;
public long mStartTime;
+ public int[] mSysMemUsageTable = null;
+ public int mSysMemUsageTableSize = 0;
+ public final long[] mSysMemUsageArgs = new long[SYS_MEM_USAGE_COUNT];
+
public long mTimePeriodStartClock;
public long mTimePeriodStartRealtime;
public long mTimePeriodEndRealtime;
+ public long mTimePeriodStartUptime;
+ public long mTimePeriodEndUptime;
String mRuntime;
- String mWebView;
boolean mRunning;
static final int LONGS_SIZE = 4096;
@@ -304,11 +331,77 @@ public final class ProcessStats implements Parcelable {
mMemFactorDurations[i] += other.mMemFactorDurations[i];
}
+ for (int i=0; i<other.mSysMemUsageTableSize; i++) {
+ int ent = other.mSysMemUsageTable[i];
+ int state = (ent>>OFFSET_TYPE_SHIFT)&OFFSET_TYPE_MASK;
+ long[] longs = other.mLongs.get((ent>>OFFSET_ARRAY_SHIFT)&OFFSET_ARRAY_MASK);
+ addSysMemUsage(state, longs, ((ent >> OFFSET_INDEX_SHIFT) & OFFSET_INDEX_MASK));
+ }
+
if (other.mTimePeriodStartClock < mTimePeriodStartClock) {
mTimePeriodStartClock = other.mTimePeriodStartClock;
mTimePeriodStartClockStr = other.mTimePeriodStartClockStr;
}
mTimePeriodEndRealtime += other.mTimePeriodEndRealtime - other.mTimePeriodStartRealtime;
+ mTimePeriodEndUptime += other.mTimePeriodEndUptime - other.mTimePeriodStartUptime;
+ }
+
+ public void addSysMemUsage(long cachedMem, long freeMem, long zramMem, long kernelMem,
+ long nativeMem) {
+ if (mMemFactor != STATE_NOTHING) {
+ int state = mMemFactor * STATE_COUNT;
+ mSysMemUsageArgs[SYS_MEM_USAGE_SAMPLE_COUNT] = 1;
+ for (int i=0; i<3; i++) {
+ mSysMemUsageArgs[SYS_MEM_USAGE_CACHED_MINIMUM + i] = cachedMem;
+ mSysMemUsageArgs[SYS_MEM_USAGE_FREE_MINIMUM + i] = freeMem;
+ mSysMemUsageArgs[SYS_MEM_USAGE_ZRAM_MINIMUM + i] = zramMem;
+ mSysMemUsageArgs[SYS_MEM_USAGE_KERNEL_MINIMUM + i] = kernelMem;
+ mSysMemUsageArgs[SYS_MEM_USAGE_NATIVE_MINIMUM + i] = nativeMem;
+ }
+ addSysMemUsage(state, mSysMemUsageArgs, 0);
+ }
+ }
+
+ void addSysMemUsage(int state, long[] data, int dataOff) {
+ int idx = binarySearch(mSysMemUsageTable, mSysMemUsageTableSize, state);
+ int off;
+ if (idx >= 0) {
+ off = mSysMemUsageTable[idx];
+ } else {
+ mAddLongTable = mSysMemUsageTable;
+ mAddLongTableSize = mSysMemUsageTableSize;
+ off = addLongData(~idx, state, SYS_MEM_USAGE_COUNT);
+ mSysMemUsageTable = mAddLongTable;
+ mSysMemUsageTableSize = mAddLongTableSize;
+ }
+ long[] longs = mLongs.get((off>>OFFSET_ARRAY_SHIFT)&OFFSET_ARRAY_MASK);
+ idx = (off>>OFFSET_INDEX_SHIFT)&OFFSET_INDEX_MASK;
+ addSysMemUsage(longs, idx, data, dataOff);
+ }
+
+ static void addSysMemUsage(long[] dstData, int dstOff, long[] addData, int addOff) {
+ final long dstCount = dstData[dstOff+SYS_MEM_USAGE_SAMPLE_COUNT];
+ final long addCount = addData[addOff+SYS_MEM_USAGE_SAMPLE_COUNT];
+ if (dstCount == 0) {
+ dstData[dstOff+SYS_MEM_USAGE_SAMPLE_COUNT] = addCount;
+ for (int i=SYS_MEM_USAGE_CACHED_MINIMUM; i<SYS_MEM_USAGE_COUNT; i++) {
+ dstData[dstOff+i] = addData[addOff+i];
+ }
+ } else if (addCount > 0) {
+ dstData[dstOff+SYS_MEM_USAGE_SAMPLE_COUNT] = dstCount + addCount;
+ for (int i=SYS_MEM_USAGE_CACHED_MINIMUM; i<SYS_MEM_USAGE_COUNT; i+=3) {
+ if (dstData[dstOff+i] > addData[addOff+i]) {
+ dstData[dstOff+i] = addData[addOff+i];
+ }
+ dstData[dstOff+i+1] = (long)(
+ ((dstData[dstOff+i+1]*(double)dstCount)
+ + (addData[addOff+i+1]*(double)addCount))
+ / (dstCount+addCount) );
+ if (dstData[dstOff+i+2] < addData[addOff+i+2]) {
+ dstData[dstOff+i+2] = addData[addOff+i+2];
+ }
+ }
+ }
}
public static final Parcelable.Creator<ProcessStats> CREATOR
@@ -564,6 +657,164 @@ public final class ProcessStats implements Parcelable {
return totalTime;
}
+ static class PssAggr {
+ long pss = 0;
+ long samples = 0;
+
+ void add(long newPss, long newSamples) {
+ pss = (long)( (pss*(double)samples) + (newPss*(double)newSamples) )
+ / (samples+newSamples);
+ samples += newSamples;
+ }
+ }
+
+ public void computeTotalMemoryUse(TotalMemoryUseCollection data, long now) {
+ data.totalTime = 0;
+ for (int i=0; i<STATE_COUNT; i++) {
+ data.processStateWeight[i] = 0;
+ data.processStatePss[i] = 0;
+ data.processStateTime[i] = 0;
+ data.processStateSamples[i] = 0;
+ }
+ for (int i=0; i<SYS_MEM_USAGE_COUNT; i++) {
+ data.sysMemUsage[i] = 0;
+ }
+ data.sysMemCachedWeight = 0;
+ data.sysMemFreeWeight = 0;
+ data.sysMemZRamWeight = 0;
+ data.sysMemKernelWeight = 0;
+ data.sysMemNativeWeight = 0;
+ data.sysMemSamples = 0;
+ long[] totalMemUsage = new long[SYS_MEM_USAGE_COUNT];
+ for (int i=0; i<mSysMemUsageTableSize; i++) {
+ int ent = mSysMemUsageTable[i];
+ long[] longs = mLongs.get((ent>>OFFSET_ARRAY_SHIFT)&OFFSET_ARRAY_MASK);
+ int idx = (ent >> OFFSET_INDEX_SHIFT) & OFFSET_INDEX_MASK;
+ addSysMemUsage(totalMemUsage, 0, longs, idx);
+ }
+ for (int is=0; is<data.screenStates.length; is++) {
+ for (int im=0; im<data.memStates.length; im++) {
+ int memBucket = data.screenStates[is] + data.memStates[im];
+ int stateBucket = memBucket * STATE_COUNT;
+ long memTime = mMemFactorDurations[memBucket];
+ if (mMemFactor == memBucket) {
+ memTime += now - mStartTime;
+ }
+ data.totalTime += memTime;
+ int sysIdx = binarySearch(mSysMemUsageTable, mSysMemUsageTableSize, stateBucket);
+ long[] longs = totalMemUsage;
+ int idx = 0;
+ if (sysIdx >= 0) {
+ int ent = mSysMemUsageTable[sysIdx];
+ long[] tmpLongs = mLongs.get((ent>>OFFSET_ARRAY_SHIFT)&OFFSET_ARRAY_MASK);
+ int tmpIdx = (ent >> OFFSET_INDEX_SHIFT) & OFFSET_INDEX_MASK;
+ if (tmpLongs[tmpIdx+SYS_MEM_USAGE_SAMPLE_COUNT] >= 3) {
+ addSysMemUsage(data.sysMemUsage, 0, longs, idx);
+ longs = tmpLongs;
+ idx = tmpIdx;
+ }
+ }
+ data.sysMemCachedWeight += longs[idx+SYS_MEM_USAGE_CACHED_AVERAGE]
+ * (double)memTime;
+ data.sysMemFreeWeight += longs[idx+SYS_MEM_USAGE_FREE_AVERAGE]
+ * (double)memTime;
+ data.sysMemZRamWeight += longs[idx+SYS_MEM_USAGE_ZRAM_AVERAGE]
+ * (double)memTime;
+ data.sysMemKernelWeight += longs[idx+SYS_MEM_USAGE_KERNEL_AVERAGE]
+ * (double)memTime;
+ data.sysMemNativeWeight += longs[idx+SYS_MEM_USAGE_NATIVE_AVERAGE]
+ * (double)memTime;
+ data.sysMemSamples += longs[idx+SYS_MEM_USAGE_SAMPLE_COUNT];
+ }
+ }
+ ArrayMap<String, SparseArray<ProcessState>> procMap = mProcesses.getMap();
+ for (int iproc=0; iproc<procMap.size(); iproc++) {
+ SparseArray<ProcessState> uids = procMap.valueAt(iproc);
+ for (int iu=0; iu<uids.size(); iu++) {
+ final ProcessState proc = uids.valueAt(iu);
+ final PssAggr fgPss = new PssAggr();
+ final PssAggr bgPss = new PssAggr();
+ final PssAggr cachedPss = new PssAggr();
+ boolean havePss = false;
+ for (int i=0; i<proc.mDurationsTableSize; i++) {
+ int off = proc.mDurationsTable[i];
+ int type = (off>>OFFSET_TYPE_SHIFT)&OFFSET_TYPE_MASK;
+ int procState = type % STATE_COUNT;
+ long samples = proc.getPssSampleCount(type);
+ if (samples > 0) {
+ long avg = proc.getPssAverage(type);
+ havePss = true;
+ if (procState <= STATE_IMPORTANT_FOREGROUND) {
+ fgPss.add(avg, samples);
+ } else if (procState <= STATE_RECEIVER) {
+ bgPss.add(avg, samples);
+ } else {
+ cachedPss.add(avg, samples);
+ }
+ }
+ }
+ if (!havePss) {
+ continue;
+ }
+ boolean fgHasBg = false;
+ boolean fgHasCached = false;
+ boolean bgHasCached = false;
+ if (fgPss.samples < 3 && bgPss.samples > 0) {
+ fgHasBg = true;
+ fgPss.add(bgPss.pss, bgPss.samples);
+ }
+ if (fgPss.samples < 3 && cachedPss.samples > 0) {
+ fgHasCached = true;
+ fgPss.add(cachedPss.pss, cachedPss.samples);
+ }
+ if (bgPss.samples < 3 && cachedPss.samples > 0) {
+ bgHasCached = true;
+ bgPss.add(cachedPss.pss, cachedPss.samples);
+ }
+ if (bgPss.samples < 3 && !fgHasBg && fgPss.samples > 0) {
+ bgPss.add(fgPss.pss, fgPss.samples);
+ }
+ if (cachedPss.samples < 3 && !bgHasCached && bgPss.samples > 0) {
+ cachedPss.add(bgPss.pss, bgPss.samples);
+ }
+ if (cachedPss.samples < 3 && !fgHasCached && fgPss.samples > 0) {
+ cachedPss.add(fgPss.pss, fgPss.samples);
+ }
+ for (int i=0; i<proc.mDurationsTableSize; i++) {
+ final int off = proc.mDurationsTable[i];
+ final int type = (off>>OFFSET_TYPE_SHIFT)&OFFSET_TYPE_MASK;
+ long time = getLong(off, 0);
+ if (proc.mCurState == type) {
+ time += now - proc.mStartTime;
+ }
+ final int procState = type % STATE_COUNT;
+ data.processStateTime[procState] += time;
+ long samples = proc.getPssSampleCount(type);
+ long avg;
+ if (samples > 0) {
+ avg = proc.getPssAverage(type);
+ } else if (procState <= STATE_IMPORTANT_FOREGROUND) {
+ samples = fgPss.samples;
+ avg = fgPss.pss;
+ } else if (procState <= STATE_RECEIVER) {
+ samples = bgPss.samples;
+ avg = bgPss.pss;
+ } else {
+ samples = cachedPss.samples;
+ avg = cachedPss.pss;
+ }
+ double newAvg = ( (data.processStatePss[procState]
+ * (double)data.processStateSamples[procState])
+ + (avg*(double)samples)
+ ) / (data.processStateSamples[procState]+samples);
+ data.processStatePss[procState] = (long)newAvg;
+ data.processStateSamples[procState] += samples;
+ data.processStateWeight[procState] += avg * (double)time;
+ }
+ }
+ }
+ }
+
static void dumpProcessState(PrintWriter pw, String prefix, ProcessState proc,
int[] screenStates, int[] memStates, int[] procStates, long now) {
long totalTime = 0;
@@ -679,6 +930,62 @@ public final class ProcessStats implements Parcelable {
}
}
+ long getSysMemUsageValue(int state, int index) {
+ int idx = binarySearch(mSysMemUsageTable, mSysMemUsageTableSize, state);
+ return idx >= 0 ? getLong(mSysMemUsageTable[idx], index) : 0;
+ }
+
+ void dumpSysMemUsageCategory(PrintWriter pw, String prefix, String label,
+ int bucket, int index) {
+ pw.print(prefix); pw.print(label);
+ pw.print(": ");
+ printSizeValue(pw, getSysMemUsageValue(bucket, index) * 1024);
+ pw.print(" min, ");
+ printSizeValue(pw, getSysMemUsageValue(bucket, index + 1) * 1024);
+ pw.print(" avg, ");
+ printSizeValue(pw, getSysMemUsageValue(bucket, index+2) * 1024);
+ pw.println(" max");
+ }
+
+ void dumpSysMemUsage(PrintWriter pw, String prefix, int[] screenStates,
+ int[] memStates) {
+ int printedScreen = -1;
+ for (int is=0; is<screenStates.length; is++) {
+ int printedMem = -1;
+ for (int im=0; im<memStates.length; im++) {
+ final int iscreen = screenStates[is];
+ final int imem = memStates[im];
+ final int bucket = ((iscreen + imem) * STATE_COUNT);
+ long count = getSysMemUsageValue(bucket, SYS_MEM_USAGE_SAMPLE_COUNT);
+ if (count > 0) {
+ pw.print(prefix);
+ if (screenStates.length > 1) {
+ printScreenLabel(pw, printedScreen != iscreen
+ ? iscreen : STATE_NOTHING);
+ printedScreen = iscreen;
+ }
+ if (memStates.length > 1) {
+ printMemLabel(pw, printedMem != imem ? imem : STATE_NOTHING, '\0');
+ printedMem = imem;
+ }
+ pw.print(": ");
+ pw.print(count);
+ pw.println(" samples:");
+ dumpSysMemUsageCategory(pw, prefix, " Cached", bucket,
+ SYS_MEM_USAGE_CACHED_MINIMUM);
+ dumpSysMemUsageCategory(pw, prefix, " Free", bucket,
+ SYS_MEM_USAGE_FREE_MINIMUM);
+ dumpSysMemUsageCategory(pw, prefix, " ZRam", bucket,
+ SYS_MEM_USAGE_ZRAM_MINIMUM);
+ dumpSysMemUsageCategory(pw, prefix, " Kernel", bucket,
+ SYS_MEM_USAGE_KERNEL_MINIMUM);
+ dumpSysMemUsageCategory(pw, prefix, " Native", bucket,
+ SYS_MEM_USAGE_NATIVE_MINIMUM);
+ }
+ }
+ }
+ }
+
static void dumpStateHeadersCsv(PrintWriter pw, String sep, int[] screenStates,
int[] memStates, int[] procStates) {
final int NS = screenStates != null ? screenStates.length : 1;
@@ -1088,10 +1395,13 @@ public final class ProcessStats implements Parcelable {
mTimePeriodStartClock = System.currentTimeMillis();
buildTimePeriodStartClockStr();
mTimePeriodStartRealtime = mTimePeriodEndRealtime = SystemClock.elapsedRealtime();
+ mTimePeriodStartUptime = mTimePeriodEndUptime = SystemClock.uptimeMillis();
mLongs.clear();
mLongs.add(new long[LONGS_SIZE]);
mNextLong = 0;
Arrays.fill(mMemFactorDurations, 0);
+ mSysMemUsageTable = null;
+ mSysMemUsageTableSize = 0;
mStartTime = 0;
mReadError = null;
mFlags = 0;
@@ -1220,12 +1530,17 @@ public final class ProcessStats implements Parcelable {
@Override
public void writeToParcel(Parcel out, int flags) {
- long now = SystemClock.uptimeMillis();
+ writeToParcel(out, SystemClock.uptimeMillis(), flags);
+ }
+
+ /** @hide */
+ public void writeToParcel(Parcel out, long now, int flags) {
out.writeInt(MAGIC);
out.writeInt(PARCEL_VERSION);
out.writeInt(STATE_COUNT);
out.writeInt(ADJ_COUNT);
out.writeInt(PSS_COUNT);
+ out.writeInt(SYS_MEM_USAGE_COUNT);
out.writeInt(LONGS_SIZE);
mCommonStringToIndex = new ArrayMap<String, Integer>(mProcesses.mMap.size());
@@ -1268,8 +1583,9 @@ public final class ProcessStats implements Parcelable {
out.writeLong(mTimePeriodStartClock);
out.writeLong(mTimePeriodStartRealtime);
out.writeLong(mTimePeriodEndRealtime);
+ out.writeLong(mTimePeriodStartUptime);
+ out.writeLong(mTimePeriodEndUptime);
out.writeString(mRuntime);
- out.writeString(mWebView);
out.writeInt(mFlags);
out.writeInt(mLongs.size());
@@ -1287,6 +1603,13 @@ public final class ProcessStats implements Parcelable {
}
writeCompactedLongArray(out, mMemFactorDurations, mMemFactorDurations.length);
+ out.writeInt(mSysMemUsageTableSize);
+ for (int i=0; i<mSysMemUsageTableSize; i++) {
+ if (DEBUG_PARCEL) Slog.i(TAG, "Writing sys mem usage #" + i + ": "
+ + printLongOffset(mSysMemUsageTable[i]));
+ out.writeInt(mSysMemUsageTable[i]);
+ }
+
out.writeInt(NPROC);
for (int ip=0; ip<NPROC; ip++) {
writeCommonString(out, procMap.keyAt(ip));
@@ -1417,6 +1740,9 @@ public final class ProcessStats implements Parcelable {
if (!readCheckedInt(in, PSS_COUNT, "pss count")) {
return;
}
+ if (!readCheckedInt(in, SYS_MEM_USAGE_COUNT, "sys mem usage count")) {
+ return;
+ }
if (!readCheckedInt(in, LONGS_SIZE, "longs size")) {
return;
}
@@ -1427,8 +1753,9 @@ public final class ProcessStats implements Parcelable {
buildTimePeriodStartClockStr();
mTimePeriodStartRealtime = in.readLong();
mTimePeriodEndRealtime = in.readLong();
+ mTimePeriodStartUptime = in.readLong();
+ mTimePeriodEndUptime = in.readLong();
mRuntime = in.readString();
- mWebView = in.readString();
mFlags = in.readInt();
final int NLONGS = in.readInt();
@@ -1447,6 +1774,12 @@ public final class ProcessStats implements Parcelable {
readCompactedLongArray(in, version, mMemFactorDurations, mMemFactorDurations.length);
+ mSysMemUsageTable = readTableFromParcel(in, TAG, "sys mem usage");
+ if (mSysMemUsageTable == BAD_TABLE) {
+ return;
+ }
+ mSysMemUsageTableSize = mSysMemUsageTable != null ? mSysMemUsageTable.length : 0;
+
int NPROC = in.readInt();
if (NPROC < 0) {
mReadError = "bad process count: " + NPROC;
@@ -1826,6 +2159,10 @@ public final class ProcessStats implements Parcelable {
boolean dumpAll, boolean activeOnly) {
long totalTime = dumpSingleTime(null, null, mMemFactorDurations, mMemFactor,
mStartTime, now);
+ if (mSysMemUsageTable != null) {
+ pw.println("System memory usage:");
+ dumpSysMemUsage(pw, " ", ALL_SCREEN_ADJ, ALL_MEM_ADJ);
+ }
ArrayMap<String, SparseArray<SparseArray<PackageState>>> pkgMap = mPackages.getMap();
boolean printedHeader = false;
boolean sepNeeded = false;
@@ -2089,10 +2426,57 @@ public final class ProcessStats implements Parcelable {
dumpTotalsLocked(pw, now);
}
+ long printMemoryCategory(PrintWriter pw, String prefix, String label, double memWeight,
+ long totalTime, long curTotalMem, int samples) {
+ if (memWeight != 0) {
+ long mem = (long)(memWeight * 1024 / totalTime);
+ pw.print(prefix);
+ pw.print(label);
+ pw.print(": ");
+ printSizeValue(pw, mem);
+ pw.print(" (");
+ pw.print(samples);
+ pw.print(" samples)");
+ pw.println();
+ return curTotalMem + mem;
+ }
+ return curTotalMem;
+ }
+
void dumpTotalsLocked(PrintWriter pw, long now) {
pw.println("Run time Stats:");
dumpSingleTime(pw, " ", mMemFactorDurations, mMemFactor, mStartTime, now);
pw.println();
+ pw.println("Memory usage:");
+ TotalMemoryUseCollection totalMem = new TotalMemoryUseCollection(ALL_SCREEN_ADJ,
+ ALL_MEM_ADJ);
+ computeTotalMemoryUse(totalMem, now);
+ long totalPss = 0;
+ totalPss = printMemoryCategory(pw, " ", "Kernel ", totalMem.sysMemKernelWeight,
+ totalMem.totalTime, totalPss, totalMem.sysMemSamples);
+ totalPss = printMemoryCategory(pw, " ", "Native ", totalMem.sysMemNativeWeight,
+ totalMem.totalTime, totalPss, totalMem.sysMemSamples);
+ for (int i=0; i<STATE_COUNT; i++) {
+ // Skip restarting service state -- that is not actually a running process.
+ if (i != STATE_SERVICE_RESTARTING) {
+ totalPss = printMemoryCategory(pw, " ", STATE_NAMES[i],
+ totalMem.processStateWeight[i], totalMem.totalTime, totalPss,
+ totalMem.processStateSamples[i]);
+ }
+ }
+ totalPss = printMemoryCategory(pw, " ", "Cached ", totalMem.sysMemCachedWeight,
+ totalMem.totalTime, totalPss, totalMem.sysMemSamples);
+ totalPss = printMemoryCategory(pw, " ", "Free ", totalMem.sysMemFreeWeight,
+ totalMem.totalTime, totalPss, totalMem.sysMemSamples);
+ totalPss = printMemoryCategory(pw, " ", "Z-Ram ", totalMem.sysMemZRamWeight,
+ totalMem.totalTime, totalPss, totalMem.sysMemSamples);
+ pw.print(" TOTAL : ");
+ printSizeValue(pw, totalPss);
+ pw.println();
+ printMemoryCategory(pw, " ", STATE_NAMES[STATE_SERVICE_RESTARTING],
+ totalMem.processStateWeight[STATE_SERVICE_RESTARTING], totalMem.totalTime, totalPss,
+ totalMem.processStateSamples[STATE_SERVICE_RESTARTING]);
+ pw.println();
pw.print(" Start time: ");
pw.print(DateFormat.format("yyyy-MM-dd HH:mm:ss", mTimePeriodStartClock));
pw.println();
@@ -2118,8 +2502,6 @@ public final class ProcessStats implements Parcelable {
}
pw.print(' ');
pw.print(mRuntime);
- pw.print(' ');
- pw.print(mWebView);
pw.println();
}
@@ -2208,7 +2590,7 @@ public final class ProcessStats implements Parcelable {
public void dumpCheckinLocked(PrintWriter pw, String reqPackage) {
final long now = SystemClock.uptimeMillis();
final ArrayMap<String, SparseArray<SparseArray<PackageState>>> pkgMap = mPackages.getMap();
- pw.println("vers,4");
+ pw.println("vers,5");
pw.print("period,"); pw.print(mTimePeriodStartClockStr);
pw.print(","); pw.print(mTimePeriodStartRealtime); pw.print(",");
pw.print(mRunning ? SystemClock.elapsedRealtime() : mTimePeriodEndRealtime);
@@ -2229,7 +2611,7 @@ public final class ProcessStats implements Parcelable {
pw.print(",partial");
}
pw.println();
- pw.print("config,"); pw.print(mRuntime); pw.print(','); pw.println(mWebView);
+ pw.print("config,"); pw.println(mRuntime);
for (int ip=0; ip<pkgMap.size(); ip++) {
final String pkgName = pkgMap.keyAt(ip);
if (reqPackage != null && !reqPackage.equals(pkgName)) {
@@ -2362,6 +2744,53 @@ public final class ProcessStats implements Parcelable {
pw.print("total");
dumpAdjTimesCheckin(pw, ",", mMemFactorDurations, mMemFactor,
mStartTime, now);
+ if (mSysMemUsageTable != null) {
+ pw.print("sysmemusage");
+ for (int i=0; i<mSysMemUsageTableSize; i++) {
+ int off = mSysMemUsageTable[i];
+ int type = (off>>OFFSET_TYPE_SHIFT)&OFFSET_TYPE_MASK;
+ pw.print(",");
+ printProcStateTag(pw, type);
+ for (int j=SYS_MEM_USAGE_SAMPLE_COUNT; j<SYS_MEM_USAGE_COUNT; j++) {
+ if (j > SYS_MEM_USAGE_CACHED_MINIMUM) {
+ pw.print(":");
+ }
+ pw.print(getLong(off, j));
+ }
+ }
+ }
+ pw.println();
+ TotalMemoryUseCollection totalMem = new TotalMemoryUseCollection(ALL_SCREEN_ADJ,
+ ALL_MEM_ADJ);
+ computeTotalMemoryUse(totalMem, now);
+ pw.print("weights,");
+ pw.print(totalMem.totalTime);
+ pw.print(",");
+ pw.print(totalMem.sysMemCachedWeight);
+ pw.print(":");
+ pw.print(totalMem.sysMemSamples);
+ pw.print(",");
+ pw.print(totalMem.sysMemFreeWeight);
+ pw.print(":");
+ pw.print(totalMem.sysMemSamples);
+ pw.print(",");
+ pw.print(totalMem.sysMemZRamWeight);
+ pw.print(":");
+ pw.print(totalMem.sysMemSamples);
+ pw.print(",");
+ pw.print(totalMem.sysMemKernelWeight);
+ pw.print(":");
+ pw.print(totalMem.sysMemSamples);
+ pw.print(",");
+ pw.print(totalMem.sysMemNativeWeight);
+ pw.print(":");
+ pw.print(totalMem.sysMemSamples);
+ for (int i=0; i<STATE_COUNT; i++) {
+ pw.print(",");
+ pw.print(totalMem.processStateWeight[i]);
+ pw.print(":");
+ pw.print(totalMem.processStateSamples[i]);
+ }
pw.println();
}
@@ -2452,6 +2881,15 @@ public final class ProcessStats implements Parcelable {
}
}
+ final public static class ProcessStateHolder {
+ public final int appVersion;
+ public ProcessStats.ProcessState state;
+
+ public ProcessStateHolder(int _appVersion) {
+ appVersion = _appVersion;
+ }
+ }
+
public static final class ProcessState extends DurationsTable {
public ProcessState mCommonProcess;
public final String mPackage;
@@ -2660,7 +3098,7 @@ public final class ProcessStats implements Parcelable {
* @param pkgList Processes to update.
*/
public void setState(int state, int memFactor, long now,
- ArrayMap<String, ProcessState> pkgList) {
+ ArrayMap<String, ProcessStateHolder> pkgList) {
if (state < 0) {
state = mNumStartedServices > 0
? (STATE_SERVICE_RESTARTING+(memFactor*STATE_COUNT)) : STATE_NOTHING;
@@ -2770,7 +3208,7 @@ public final class ProcessStats implements Parcelable {
}
public void addPss(long pss, long uss, boolean always,
- ArrayMap<String, ProcessState> pkgList) {
+ ArrayMap<String, ProcessStateHolder> pkgList) {
ensureNotDead();
if (!always) {
if (mLastPssState == mCurState && SystemClock.uptimeMillis()
@@ -2845,7 +3283,7 @@ public final class ProcessStats implements Parcelable {
}
}
- public void reportExcessiveWake(ArrayMap<String, ProcessState> pkgList) {
+ public void reportExcessiveWake(ArrayMap<String, ProcessStateHolder> pkgList) {
ensureNotDead();
mCommonProcess.mNumExcessiveWake++;
if (!mCommonProcess.mMultiPackage) {
@@ -2857,7 +3295,7 @@ public final class ProcessStats implements Parcelable {
}
}
- public void reportExcessiveCpu(ArrayMap<String, ProcessState> pkgList) {
+ public void reportExcessiveCpu(ArrayMap<String, ProcessStateHolder> pkgList) {
ensureNotDead();
mCommonProcess.mNumExcessiveCpu++;
if (!mCommonProcess.mMultiPackage) {
@@ -2888,7 +3326,7 @@ public final class ProcessStats implements Parcelable {
}
}
- public void reportCachedKill(ArrayMap<String, ProcessState> pkgList, long pss) {
+ public void reportCachedKill(ArrayMap<String, ProcessStateHolder> pkgList, long pss) {
ensureNotDead();
mCommonProcess.addCachedKill(1, pss, pss, pss);
if (!mCommonProcess.mMultiPackage) {
@@ -2925,8 +3363,10 @@ public final class ProcessStats implements Parcelable {
return this;
}
- private ProcessState pullFixedProc(ArrayMap<String, ProcessState> pkgList, int index) {
- ProcessState proc = pkgList.valueAt(index);
+ private ProcessState pullFixedProc(ArrayMap<String, ProcessStateHolder> pkgList,
+ int index) {
+ ProcessStateHolder holder = pkgList.valueAt(index);
+ ProcessState proc = holder.state;
if (mDead && proc.mCommonProcess != proc) {
// Somehow we are contining to use a process state that is dead, because
// it was not being told it was active during the last commit. We can recover
@@ -2959,7 +3399,7 @@ public final class ProcessStats implements Parcelable {
throw new IllegalStateException("Didn't create per-package process "
+ proc.mName + " in pkg " + pkg.mPackageName + "/" + pkg.mUid);
}
- pkgList.setValueAt(index, proc);
+ holder.state = proc;
}
return proc;
}
@@ -3351,4 +3791,27 @@ public final class ProcessStats implements Parcelable {
}
}
}
+
+ public static class TotalMemoryUseCollection {
+ final int[] screenStates;
+ final int[] memStates;
+
+ public TotalMemoryUseCollection(int[] _screenStates, int[] _memStates) {
+ screenStates = _screenStates;
+ memStates = _memStates;
+ }
+
+ public long totalTime;
+ public long[] processStatePss = new long[STATE_COUNT];
+ public double[] processStateWeight = new double[STATE_COUNT];
+ public long[] processStateTime = new long[STATE_COUNT];
+ public int[] processStateSamples = new int[STATE_COUNT];
+ public long[] sysMemUsage = new long[SYS_MEM_USAGE_COUNT];
+ public double sysMemCachedWeight;
+ public double sysMemFreeWeight;
+ public double sysMemZRamWeight;
+ public double sysMemKernelWeight;
+ public double sysMemNativeWeight;
+ public int sysMemSamples;
+ }
}
diff --git a/core/java/com/android/internal/app/WindowDecorActionBar.java b/core/java/com/android/internal/app/WindowDecorActionBar.java
index 87a80ac..7bd316f 100644
--- a/core/java/com/android/internal/app/WindowDecorActionBar.java
+++ b/core/java/com/android/internal/app/WindowDecorActionBar.java
@@ -830,7 +830,9 @@ public class WindowDecorActionBar extends ActionBar implements
}
public boolean isShowing() {
- return mNowShowing && getHideOffset() < getHeight();
+ final int height = getHeight();
+ // Take into account the case where the bar has a 0 height due to not being measured yet.
+ return mNowShowing && (height == 0 || getHideOffset() < height);
}
void animateToMode(boolean toActionMode) {
diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java
index ff0ee65..b098de8 100644
--- a/core/java/com/android/internal/backup/LocalTransport.java
+++ b/core/java/com/android/internal/backup/LocalTransport.java
@@ -93,6 +93,9 @@ public class LocalTransport extends BackupTransport {
private File mFullRestoreSetDir;
private HashSet<String> mFullRestorePackages;
+ private FileInputStream mCurFullRestoreStream;
+ private FileOutputStream mFullRestoreSocketStream;
+ private byte[] mFullRestoreBuffer;
public LocalTransport(Context context) {
mContext = context;
@@ -104,34 +107,41 @@ public class LocalTransport extends BackupTransport {
}
}
+ @Override
public String name() {
return new ComponentName(mContext, this.getClass()).flattenToShortString();
}
+ @Override
public Intent configurationIntent() {
// The local transport is not user-configurable
return null;
}
+ @Override
public String currentDestinationString() {
return TRANSPORT_DESTINATION_STRING;
}
+ @Override
public String transportDirName() {
return TRANSPORT_DIR_NAME;
}
+ @Override
public long requestBackupTime() {
// any time is a good time for local backup
return 0;
}
+ @Override
public int initializeDevice() {
if (DEBUG) Log.v(TAG, "wiping all data");
deleteContents(mCurrentSetDir);
- return BackupTransport.TRANSPORT_OK;
+ return TRANSPORT_OK;
}
+ @Override
public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor data) {
if (DEBUG) {
try {
@@ -191,7 +201,7 @@ public class LocalTransport extends BackupTransport {
entity.write(buf, 0, dataSize);
} catch (IOException e) {
Log.e(TAG, "Unable to update key file " + entityFile.getAbsolutePath());
- return BackupTransport.TRANSPORT_ERROR;
+ return TRANSPORT_ERROR;
} finally {
entity.close();
}
@@ -199,11 +209,11 @@ public class LocalTransport extends BackupTransport {
entityFile.delete();
}
}
- return BackupTransport.TRANSPORT_OK;
+ return TRANSPORT_OK;
} catch (IOException e) {
// oops, something went wrong. abort the operation and return error.
Log.v(TAG, "Exception reading backup input:", e);
- return BackupTransport.TRANSPORT_ERROR;
+ return TRANSPORT_ERROR;
}
}
@@ -222,6 +232,7 @@ public class LocalTransport extends BackupTransport {
}
}
+ @Override
public int clearBackupData(PackageInfo packageInfo) {
if (DEBUG) Log.v(TAG, "clearBackupData() pkg=" + packageInfo.packageName);
@@ -243,9 +254,10 @@ public class LocalTransport extends BackupTransport {
packageDir.delete();
}
- return BackupTransport.TRANSPORT_OK;
+ return TRANSPORT_OK;
}
+ @Override
public int finishBackup() {
if (DEBUG) Log.v(TAG, "finishBackup()");
if (mSocket != null) {
@@ -259,24 +271,27 @@ public class LocalTransport extends BackupTransport {
mFullTargetPackage = null;
mSocket.close();
} catch (IOException e) {
- return BackupTransport.TRANSPORT_ERROR;
+ return TRANSPORT_ERROR;
} finally {
mSocket = null;
}
}
- return BackupTransport.TRANSPORT_OK;
+ return TRANSPORT_OK;
}
// ------------------------------------------------------------------------------------
// Full backup handling
+
+ @Override
public long requestFullBackupTime() {
return 0;
}
+ @Override
public int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor socket) {
if (mSocket != null) {
Log.e(TAG, "Attempt to initiate full backup while one is in progress");
- return BackupTransport.TRANSPORT_ERROR;
+ return TRANSPORT_ERROR;
}
if (DEBUG) {
@@ -291,7 +306,7 @@ public class LocalTransport extends BackupTransport {
mSocketInputStream = new FileInputStream(mSocket.getFileDescriptor());
} catch (IOException e) {
Log.e(TAG, "Unable to process socket for full backup");
- return BackupTransport.TRANSPORT_ERROR;
+ return TRANSPORT_ERROR;
}
mFullTargetPackage = targetPackage.packageName;
@@ -300,18 +315,19 @@ public class LocalTransport extends BackupTransport {
File tarball = new File(mCurrentSetFullDir, mFullTargetPackage);
tarstream = new FileOutputStream(tarball);
} catch (FileNotFoundException e) {
- return BackupTransport.TRANSPORT_ERROR;
+ return TRANSPORT_ERROR;
}
mFullBackupOutputStream = new BufferedOutputStream(tarstream);
mFullBackupBuffer = new byte[4096];
- return BackupTransport.TRANSPORT_OK;
+ return TRANSPORT_OK;
}
+ @Override
public int sendBackupData(int numBytes) {
if (mFullBackupBuffer == null) {
Log.w(TAG, "Attempted sendBackupData before performFullBackup");
- return BackupTransport.TRANSPORT_ERROR;
+ return TRANSPORT_ERROR;
}
if (numBytes > mFullBackupBuffer.length) {
@@ -323,21 +339,23 @@ public class LocalTransport extends BackupTransport {
if (nRead < 0) {
// Something went wrong if we expect data but saw EOD
Log.w(TAG, "Unexpected EOD; failing backup");
- return BackupTransport.TRANSPORT_ERROR;
+ return TRANSPORT_ERROR;
}
mFullBackupOutputStream.write(mFullBackupBuffer, 0, nRead);
numBytes -= nRead;
} catch (IOException e) {
Log.e(TAG, "Error handling backup data for " + mFullTargetPackage);
- return BackupTransport.TRANSPORT_ERROR;
+ return TRANSPORT_ERROR;
}
}
- return BackupTransport.TRANSPORT_OK;
+ return TRANSPORT_OK;
}
// ------------------------------------------------------------------------------------
// Restore handling
static final long[] POSSIBLE_SETS = { 2, 3, 4, 5, 6, 7, 8, 9 };
+
+ @Override
public RestoreSet[] getAvailableRestoreSets() {
long[] existing = new long[POSSIBLE_SETS.length + 1];
int num = 0;
@@ -358,11 +376,13 @@ public class LocalTransport extends BackupTransport {
return available;
}
+ @Override
public long getCurrentRestoreSet() {
// The current restore set always has the same token
return CURRENT_SET_TOKEN;
}
+ @Override
public int startRestore(long token, PackageInfo[] packages) {
if (DEBUG) Log.v(TAG, "start restore " + token);
mRestorePackages = packages;
@@ -371,7 +391,7 @@ public class LocalTransport extends BackupTransport {
mRestoreSetDir = new File(mDataDir, Long.toString(token));
mRestoreSetIncrementalDir = new File(mRestoreSetDir, INCREMENTAL_DIR);
mRestoreSetFullDir = new File(mRestoreSetDir, FULL_DATA_DIR);
- return BackupTransport.TRANSPORT_OK;
+ return TRANSPORT_OK;
}
@Override
@@ -397,6 +417,7 @@ public class LocalTransport extends BackupTransport {
if (maybeFullData.length() > 0) {
if (DEBUG) Log.v(TAG, " nextRestorePackage(TYPE_FULL_STREAM) = " + name);
mRestoreType = RestoreDescription.TYPE_FULL_STREAM;
+ mCurFullRestoreStream = null; // ensure starting from the ground state
found = true;
}
}
@@ -410,6 +431,7 @@ public class LocalTransport extends BackupTransport {
return RestoreDescription.NO_MORE_PACKAGES;
}
+ @Override
public int getRestoreData(ParcelFileDescriptor outFd) {
if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
if (mRestorePackage < 0) throw new IllegalStateException("nextRestorePackage not called");
@@ -426,7 +448,7 @@ public class LocalTransport extends BackupTransport {
ArrayList<DecodedFilename> blobs = contentsByKey(packageDir);
if (blobs == null) { // nextRestorePackage() ensures the dir exists, so this is an error
Log.e(TAG, "No keys for package: " + packageDir);
- return BackupTransport.TRANSPORT_ERROR;
+ return TRANSPORT_ERROR;
}
// We expect at least some data if the directory exists in the first place
@@ -447,10 +469,10 @@ public class LocalTransport extends BackupTransport {
in.close();
}
}
- return BackupTransport.TRANSPORT_OK;
+ return TRANSPORT_OK;
} catch (IOException e) {
Log.e(TAG, "Unable to read backup records", e);
- return BackupTransport.TRANSPORT_ERROR;
+ return TRANSPORT_ERROR;
}
}
@@ -487,38 +509,27 @@ public class LocalTransport extends BackupTransport {
return contents;
}
+ @Override
public void finishRestore() {
if (DEBUG) Log.v(TAG, "finishRestore()");
+ if (mRestoreType == RestoreDescription.TYPE_FULL_STREAM) {
+ resetFullRestoreState();
+ }
+ mRestoreType = 0;
}
// ------------------------------------------------------------------------------------
// Full restore handling
- public int prepareFullRestore(long token, String[] targetPackages) {
- mRestoreSetDir = new File(mDataDir, Long.toString(token));
- mFullRestoreSetDir = new File(mRestoreSetDir, FULL_DATA_DIR);
- mFullRestorePackages = new HashSet<String>();
- if (mFullRestoreSetDir.exists()) {
- List<String> pkgs = Arrays.asList(mFullRestoreSetDir.list());
- HashSet<String> available = new HashSet<String>(pkgs);
-
- for (int i = 0; i < targetPackages.length; i++) {
- if (available.contains(targetPackages[i])) {
- mFullRestorePackages.add(targetPackages[i]);
- }
- }
+ private void resetFullRestoreState() {
+ try {
+ mCurFullRestoreStream.close();
+ } catch (IOException e) {
+ Log.w(TAG, "Unable to close full restore input stream");
}
- return BackupTransport.TRANSPORT_OK;
- }
-
- /**
- * Ask the transport what package's full data will be restored next. When all apps'
- * data has been delivered, the transport should return {@code null} here.
- * @return The package name of the next application whose data will be restored, or
- * {@code null} if all available package has been delivered.
- */
- public String getNextFullRestorePackage() {
- return null;
+ mCurFullRestoreStream = null;
+ mFullRestoreSocketStream = null;
+ mFullRestoreBuffer = null;
}
/**
@@ -543,7 +554,79 @@ public class LocalTransport extends BackupTransport {
* indicating a fatal error condition that precludes further restore operations
* on the current dataset.
*/
+ @Override
public int getNextFullRestoreDataChunk(ParcelFileDescriptor socket) {
- return 0;
+ if (mRestoreType != RestoreDescription.TYPE_FULL_STREAM) {
+ throw new IllegalStateException("Asked for full restore data for non-stream package");
+ }
+
+ // first chunk?
+ if (mCurFullRestoreStream == null) {
+ final String name = mRestorePackages[mRestorePackage].packageName;
+ if (DEBUG) Log.i(TAG, "Starting full restore of " + name);
+ File dataset = new File(mRestoreSetFullDir, name);
+ try {
+ mCurFullRestoreStream = new FileInputStream(dataset);
+ } catch (IOException e) {
+ // If we can't open the target package's tarball, we return the single-package
+ // error code and let the caller go on to the next package.
+ Log.e(TAG, "Unable to read archive for " + name);
+ return TRANSPORT_PACKAGE_REJECTED;
+ }
+ mFullRestoreSocketStream = new FileOutputStream(socket.getFileDescriptor());
+ mFullRestoreBuffer = new byte[32*1024];
+ }
+
+ int nRead;
+ try {
+ nRead = mCurFullRestoreStream.read(mFullRestoreBuffer);
+ if (nRead < 0) {
+ // EOF: tell the caller we're done
+ nRead = NO_MORE_DATA;
+ } else if (nRead == 0) {
+ // This shouldn't happen when reading a FileInputStream; we should always
+ // get either a positive nonzero byte count or -1. Log the situation and
+ // treat it as EOF.
+ Log.w(TAG, "read() of archive file returned 0; treating as EOF");
+ nRead = NO_MORE_DATA;
+ } else {
+ if (DEBUG) {
+ Log.i(TAG, " delivering restore chunk: " + nRead);
+ }
+ mFullRestoreSocketStream.write(mFullRestoreBuffer, 0, nRead);
+ }
+ } catch (IOException e) {
+ return TRANSPORT_ERROR; // Hard error accessing the file; shouldn't happen
+ } finally {
+ // Most transports will need to explicitly close 'socket' here, but this transport
+ // is in the same process as the caller so it can leave it up to the backup manager
+ // to manage both socket fds.
+ }
+
+ return nRead;
}
+
+ /**
+ * If the OS encounters an error while processing {@link RestoreDescription#TYPE_FULL_STREAM}
+ * data for restore, it will invoke this method to tell the transport that it should
+ * abandon the data download for the current package. The OS will then either call
+ * {@link #nextRestorePackage()} again to move on to restoring the next package in the
+ * set being iterated over, or will call {@link #finishRestore()} to shut down the restore
+ * operation.
+ *
+ * @return {@link #TRANSPORT_OK} if the transport was successful in shutting down the
+ * current stream cleanly, or {@link #TRANSPORT_ERROR} to indicate a serious
+ * transport-level failure. If the transport reports an error here, the entire restore
+ * operation will immediately be finished with no further attempts to restore app data.
+ */
+ @Override
+ public int abortFullRestore() {
+ if (mRestoreType != RestoreDescription.TYPE_FULL_STREAM) {
+ throw new IllegalStateException("abortFullRestore() but not currently restoring");
+ }
+ resetFullRestoreState();
+ mRestoreType = 0;
+ return TRANSPORT_OK;
+ }
+
}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 240d520..1ccaa0f 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -291,6 +291,7 @@ public final class BatteryStatsImpl extends BatteryStats {
final StopwatchTimer[] mBluetoothStateTimer = new StopwatchTimer[NUM_BLUETOOTH_STATES];
int mMobileRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
+ long mMobileRadioActiveStartTime;
StopwatchTimer mMobileRadioActiveTimer;
StopwatchTimer mMobileRadioActivePerAppTimer;
LongSamplingCounter mMobileRadioActiveAdjustedTime;
@@ -1425,10 +1426,6 @@ public final class BatteryStatsImpl extends BatteryStats {
return 0;
}
- long getLastUpdateTimeMs() {
- return mUpdateTime;
- }
-
void stopRunningLocked(long elapsedRealtimeMs) {
// Ignore attempt to stop a timer that isn't running
if (mNesting == 0) {
@@ -2790,11 +2787,11 @@ public final class BatteryStatsImpl extends BatteryStats {
powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_MEDIUM
|| powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH;
if (active) {
- realElapsedRealtimeMs = elapsedRealtime;
+ mMobileRadioActiveStartTime = realElapsedRealtimeMs = elapsedRealtime;
mHistoryCur.states |= HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG;
} else {
realElapsedRealtimeMs = timestampNs / (1000*1000);
- long lastUpdateTimeMs = mMobileRadioActiveTimer.getLastUpdateTimeMs();
+ long lastUpdateTimeMs = mMobileRadioActiveStartTime;
if (realElapsedRealtimeMs < lastUpdateTimeMs) {
Slog.wtf(TAG, "Data connection inactive timestamp " + realElapsedRealtimeMs
+ " is before start time " + lastUpdateTimeMs);