diff options
Diffstat (limited to 'core/java')
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); |