Merge "Add accessibility action to open power long-press dialog"
diff --git a/api/current.txt b/api/current.txt
index 1816736..d55cac6 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -244,6 +244,7 @@
field public static final int action = 16842797; // 0x101002d
field public static final int actionBarDivider = 16843675; // 0x101039b
field public static final int actionBarItemBackground = 16843676; // 0x101039c
+ field public static final int actionBarPopupTheme = 16843919; // 0x101048f
field public static final int actionBarSize = 16843499; // 0x10102eb
field public static final int actionBarSplitStyle = 16843656; // 0x1010388
field public static final int actionBarStyle = 16843470; // 0x10102ce
@@ -852,7 +853,7 @@
field public static final int mirrorForRtl = 16843726; // 0x10103ce
field public static final int mode = 16843134; // 0x101017e
field public static final int moreIcon = 16843061; // 0x1010135
- field public static final int multiArch = 16843919; // 0x101048f
+ field public static final int multiArch = 16843920; // 0x1010490
field public static final int multiprocess = 16842771; // 0x1010013
field public static final int name = 16842755; // 0x1010003
field public static final int navigationBarColor = 16843860; // 0x1010454
@@ -3159,6 +3160,7 @@
method public abstract deprecated void addTab(android.app.ActionBar.Tab, int, boolean);
method public abstract android.view.View getCustomView();
method public abstract int getDisplayOptions();
+ method public float getElevation();
method public abstract int getHeight();
method public int getHideOffset();
method public abstract deprecated int getNavigationItemCount();
@@ -3190,6 +3192,7 @@
method public abstract void setDisplayShowHomeEnabled(boolean);
method public abstract void setDisplayShowTitleEnabled(boolean);
method public abstract void setDisplayUseLogoEnabled(boolean);
+ method public void setElevation(float);
method public void setHideOffset(int);
method public void setHideOnContentScrollEnabled(boolean);
method public void setHomeActionContentDescription(java.lang.CharSequence);
@@ -5344,6 +5347,7 @@
method public void setRestrictionsProvider(android.content.ComponentName, android.content.ComponentName);
method public void setSecureSetting(android.content.ComponentName, java.lang.String, java.lang.String);
method public int setStorageEncryption(android.content.ComponentName, boolean);
+ method public boolean switchUser(android.content.ComponentName, android.os.UserHandle);
method public void uninstallCaCert(android.content.ComponentName, byte[]);
method public void wipeData(int);
field public static final java.lang.String ACTION_ADD_DEVICE_ADMIN = "android.app.action.ADD_DEVICE_ADMIN";
@@ -5492,6 +5496,7 @@
method public int getNetworkCapabilities();
method public android.content.ComponentName getService();
method public boolean isPeriodic();
+ method public boolean isPersisted();
method public boolean isRequireCharging();
method public boolean isRequireDeviceIdle();
method public void writeToParcel(android.os.Parcel, int);
@@ -5508,6 +5513,7 @@
method public android.app.job.JobInfo build();
method public android.app.job.JobInfo.Builder setBackoffCriteria(long, int);
method public android.app.job.JobInfo.Builder setExtras(android.os.PersistableBundle);
+ method public android.app.job.JobInfo.Builder setIsPersisted(boolean);
method public android.app.job.JobInfo.Builder setMinimumLatency(long);
method public android.app.job.JobInfo.Builder setOverrideDeadline(long);
method public android.app.job.JobInfo.Builder setPeriodic(long);
@@ -12823,7 +12829,15 @@
public final class DngCreator implements java.lang.AutoCloseable {
ctor public DngCreator(android.hardware.camera2.CameraCharacteristics, android.hardware.camera2.CaptureResult);
method public void close();
+ method public android.hardware.camera2.DngCreator setDescription(java.lang.String);
+ method public android.hardware.camera2.DngCreator setLocation(android.location.Location);
+ method public android.hardware.camera2.DngCreator setOrientation(int);
+ method public android.hardware.camera2.DngCreator setThumbnail(android.graphics.Bitmap);
+ method public android.hardware.camera2.DngCreator setThumbnail(android.media.Image);
+ method public void writeByteBuffer(java.io.OutputStream, android.util.Size, java.nio.ByteBuffer, long) throws java.io.IOException;
method public void writeImage(java.io.OutputStream, android.media.Image) throws java.io.IOException;
+ method public void writeInputStream(java.io.OutputStream, android.util.Size, java.io.InputStream, long) throws java.io.IOException;
+ field public static final int MAX_THUMBNAIL_DIMENSION = 256; // 0x100
}
public final class TotalCaptureResult extends android.hardware.camera2.CaptureResult {
@@ -36102,11 +36116,13 @@
ctor public ActionMenuView(android.content.Context, android.util.AttributeSet);
method public void dismissPopupMenus();
method public android.view.Menu getMenu();
+ method public int getPopupTheme();
method public boolean hideOverflowMenu();
method public boolean isOverflowMenuShowing();
method public void onConfigurationChanged(android.content.res.Configuration);
method public void onDetachedFromWindow();
method public void setOnMenuItemClickListener(android.widget.ActionMenuView.OnMenuItemClickListener);
+ method public void setPopupTheme(int);
method public boolean showOverflowMenu();
}
@@ -38334,6 +38350,7 @@
method public android.view.Menu getMenu();
method public java.lang.CharSequence getNavigationContentDescription();
method public android.graphics.drawable.Drawable getNavigationIcon();
+ method public int getPopupTheme();
method public java.lang.CharSequence getSubtitle();
method public java.lang.CharSequence getTitle();
method public boolean hasExpandedActionView();
@@ -38355,6 +38372,7 @@
method public void setNavigationIcon(android.graphics.drawable.Drawable);
method public void setNavigationOnClickListener(android.view.View.OnClickListener);
method public void setOnMenuItemClickListener(android.widget.Toolbar.OnMenuItemClickListener);
+ method public void setPopupTheme(int);
method public void setSubtitle(int);
method public void setSubtitle(java.lang.CharSequence);
method public void setSubtitleTextAppearance(android.content.Context, int);
diff --git a/core/java/android/app/ActionBar.java b/core/java/android/app/ActionBar.java
index 5c98180..b74c824 100644
--- a/core/java/android/app/ActionBar.java
+++ b/core/java/android/app/ActionBar.java
@@ -993,6 +993,33 @@
}
}
+ /**
+ * Set the Z-axis elevation of the action bar in pixels.
+ *
+ * <p>The action bar's elevation is the distance it is placed from its parent surface. Higher
+ * values are closer to the user.</p>
+ *
+ * @param elevation Elevation value in pixels
+ */
+ public void setElevation(float elevation) {
+ if (elevation != 0) {
+ throw new UnsupportedOperationException("Setting a non-zero elevation is " +
+ "not supported in this action bar configuration.");
+ }
+ }
+
+ /**
+ * Get the Z-axis elevation of the action bar in pixels.
+ *
+ * <p>The action bar's elevation is the distance it is placed from its parent surface. Higher
+ * values are closer to the user.</p>
+ *
+ * @return Elevation value in pixels
+ */
+ public float getElevation() {
+ return 0;
+ }
+
/** @hide */
public void setDefaultDisplayHomeAsUpEnabled(boolean enabled) {
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 2feec1b..6ea6b4b 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2375,6 +2375,24 @@
}
/**
+ * Called by a device owner to switch the specified user to the foreground.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param userHandle the user to switch to; null will switch to primary.
+ * @return {@code true} if the switch was successful, {@code false} otherwise.
+ *
+ * @see Intent#ACTION_USER_FOREGROUND
+ */
+ public boolean switchUser(ComponentName admin, UserHandle userHandle) {
+ try {
+ return mService.switchUser(admin, userHandle);
+ } catch (RemoteException re) {
+ Log.w(TAG, "Could not switch user ", re);
+ return false;
+ }
+ }
+
+ /**
* Called by a profile or device owner to get the application restrictions for a given target
* application running in the managed profile.
*
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 40bd7d1..c27d1cc 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -139,6 +139,7 @@
UserHandle createUser(in ComponentName who, in String name);
UserHandle createAndInitializeUser(in ComponentName who, in String name, in String profileOwnerName, in ComponentName profileOwnerComponent, in Bundle adminExtras);
boolean removeUser(in ComponentName who, in UserHandle userHandle);
+ boolean switchUser(in ComponentName who, in UserHandle userHandle);
void setAccountManagementDisabled(in ComponentName who, in String accountType, in boolean disabled);
String[] getAccountTypesWithManagementDisabled();
diff --git a/core/java/android/app/job/JobInfo.java b/core/java/android/app/job/JobInfo.java
index a22e4cd..70f6966 100644
--- a/core/java/android/app/job/JobInfo.java
+++ b/core/java/android/app/job/JobInfo.java
@@ -64,7 +64,6 @@
}
private final int jobId;
- // TODO: Change this to use PersistableBundle when that lands in master.
private final PersistableBundle extras;
private final ComponentName service;
private final boolean requireCharging;
@@ -75,6 +74,7 @@
private final long minLatencyMillis;
private final long maxExecutionDelayMillis;
private final boolean isPeriodic;
+ private final boolean isPersisted;
private final long intervalMillis;
private final long initialBackoffMillis;
private final int backoffPolicy;
@@ -145,6 +145,13 @@
}
/**
+ * @return Whether or not this job should be persisted across device reboots.
+ */
+ public boolean isPersisted() {
+ return isPersisted;
+ }
+
+ /**
* Set to the interval between occurrences of this job. This value is <b>not</b> set if the
* job does not recur periodically.
*/
@@ -197,6 +204,7 @@
minLatencyMillis = in.readLong();
maxExecutionDelayMillis = in.readLong();
isPeriodic = in.readInt() == 1;
+ isPersisted = in.readInt() == 1;
intervalMillis = in.readLong();
initialBackoffMillis = in.readLong();
backoffPolicy = in.readInt();
@@ -214,6 +222,7 @@
minLatencyMillis = b.mMinLatencyMillis;
maxExecutionDelayMillis = b.mMaxExecutionDelayMillis;
isPeriodic = b.mIsPeriodic;
+ isPersisted = b.mIsPersisted;
intervalMillis = b.mIntervalMillis;
initialBackoffMillis = b.mInitialBackoffMillis;
backoffPolicy = b.mBackoffPolicy;
@@ -237,6 +246,7 @@
out.writeLong(minLatencyMillis);
out.writeLong(maxExecutionDelayMillis);
out.writeInt(isPeriodic ? 1 : 0);
+ out.writeInt(isPersisted ? 1 : 0);
out.writeLong(intervalMillis);
out.writeLong(initialBackoffMillis);
out.writeInt(backoffPolicy);
@@ -265,6 +275,7 @@
private boolean mRequiresCharging;
private boolean mRequiresDeviceIdle;
private int mNetworkCapabilities;
+ private boolean mIsPersisted;
// One-off parameters.
private long mMinLatencyMillis;
private long mMaxExecutionDelayMillis;
@@ -342,11 +353,6 @@
* Specify that this job should recur with the provided interval, not more than once per
* period. You have no control over when within this interval this job will be executed,
* only the guarantee that it will be executed at most once within this interval.
- * A periodic job will be repeated until the phone is turned off, however it will only be
- * persisted beyond boot if the client app has declared the
- * {@link android.Manifest.permission#RECEIVE_BOOT_COMPLETED} permission. You can schedule
- * periodic jobs without this permission, they simply will cease to exist after the phone
- * restarts.
* Setting this function on the builder with {@link #setMinimumLatency(long)} or
* {@link #setOverrideDeadline(long)} will result in an error.
* @param intervalMillis Millisecond interval for which this job will repeat.
@@ -407,6 +413,19 @@
}
/**
+ * Set whether or not to persist this job across device reboots. This will only have an
+ * effect if your application holds the permission
+ * {@link android.Manifest.permission#RECEIVE_BOOT_COMPLETED}. Otherwise an exception will
+ * be thrown.
+ * @param isPersisted True to indicate that the job will be written to disk and loaded at
+ * boot.
+ */
+ public Builder setIsPersisted(boolean isPersisted) {
+ mIsPersisted = isPersisted;
+ return this;
+ }
+
+ /**
* @return The job object to hand to the JobScheduler. This object is immutable.
*/
public JobInfo build() {
diff --git a/core/java/android/hardware/camera2/DngCreator.java b/core/java/android/hardware/camera2/DngCreator.java
index 3e3303c..6fc99ac 100644
--- a/core/java/android/hardware/camera2/DngCreator.java
+++ b/core/java/android/hardware/camera2/DngCreator.java
@@ -17,6 +17,7 @@
package android.hardware.camera2;
import android.graphics.Bitmap;
+import android.graphics.Color;
import android.graphics.ImageFormat;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.location.Location;
@@ -31,6 +32,7 @@
import java.nio.ByteBuffer;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
+import java.util.Calendar;
import java.util.TimeZone;
/**
@@ -68,17 +70,19 @@
* </p>
* <p>
* DNG metadata tags will be generated from the corresponding parameters in the
- * {@link android.hardware.camera2.CaptureResult} object. This removes or overrides
- * all previous tags set.
+ * {@link android.hardware.camera2.CaptureResult} object.
* </p>
- *
+ * <p>
+ * For best quality DNG files, it is strongly recommended that lens shading map output is
+ * enabled if supported. See {@link CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE}.
+ * </p>
* @param characteristics an object containing the static
* {@link android.hardware.camera2.CameraCharacteristics}.
* @param metadata a metadata object to generate tags from.
*/
public DngCreator(CameraCharacteristics characteristics, CaptureResult metadata) {
if (characteristics == null || metadata == null) {
- throw new NullPointerException("Null argument to DngCreator constructor");
+ throw new IllegalArgumentException("Null argument to DngCreator constructor");
}
// Find current time
@@ -121,10 +125,8 @@
* <li>{@link android.media.ExifInterface#ORIENTATION_ROTATE_270}</li>
* </ul>
* @return this {@link #DngCreator} object.
- * @hide
*/
public DngCreator setOrientation(int orientation) {
-
if (orientation < ExifInterface.ORIENTATION_UNDEFINED ||
orientation > ExifInterface.ORIENTATION_ROTATE_270) {
throw new IllegalArgumentException("Orientation " + orientation +
@@ -139,32 +141,32 @@
*
* <p>
* Pixel data will be converted to a Baseline TIFF RGB image, with 8 bits per color channel.
- * The alpha channel will be discarded.
- * </p>
- *
- * <p>
- * The given bitmap should not be altered while this object is in use.
+ * The alpha channel will be discarded. Thumbnail images with a dimension larger than
+ * {@link #MAX_THUMBNAIL_DIMENSION} will be rejected.
* </p>
*
* @param pixels a {@link android.graphics.Bitmap} of pixel data.
* @return this {@link #DngCreator} object.
- * @hide
+ * @throws java.lang.IllegalArgumentException if the given thumbnail image has a dimension
+ * larger than {@link #MAX_THUMBNAIL_DIMENSION}.
*/
public DngCreator setThumbnail(Bitmap pixels) {
if (pixels == null) {
- throw new NullPointerException("Null argument to setThumbnail");
+ throw new IllegalArgumentException("Null argument to setThumbnail");
}
- Bitmap.Config config = pixels.getConfig();
+ int width = pixels.getWidth();
+ int height = pixels.getHeight();
- if (config != Bitmap.Config.ARGB_8888) {
- pixels = pixels.copy(Bitmap.Config.ARGB_8888, false);
- if (pixels == null) {
- throw new IllegalArgumentException("Unsupported Bitmap format " + config);
- }
- nativeSetThumbnailBitmap(pixels);
+ if (width > MAX_THUMBNAIL_DIMENSION || height > MAX_THUMBNAIL_DIMENSION) {
+ throw new IllegalArgumentException("Thumbnail dimensions width,height (" + width +
+ "," + height + ") too large, dimensions must be smaller than " +
+ MAX_THUMBNAIL_DIMENSION);
}
+ ByteBuffer rgbBuffer = convertToRGB(pixels);
+ nativeSetThumbnail(rgbBuffer, width, height);
+
return this;
}
@@ -173,37 +175,41 @@
*
* <p>
* Pixel data is interpreted as a {@link android.graphics.ImageFormat#YUV_420_888} image.
- * </p>
- *
- * <p>
- * The given image should not be altered while this object is in use.
+ * Thumbnail images with a dimension larger than {@link #MAX_THUMBNAIL_DIMENSION} will be
+ * rejected.
* </p>
*
* @param pixels an {@link android.media.Image} object with the format
* {@link android.graphics.ImageFormat#YUV_420_888}.
* @return this {@link #DngCreator} object.
- * @hide
+ * @throws java.lang.IllegalArgumentException if the given thumbnail image has a dimension
+ * larger than {@link #MAX_THUMBNAIL_DIMENSION}.
*/
public DngCreator setThumbnail(Image pixels) {
if (pixels == null) {
- throw new NullPointerException("Null argument to setThumbnail");
+ throw new IllegalArgumentException("Null argument to setThumbnail");
}
int format = pixels.getFormat();
if (format != ImageFormat.YUV_420_888) {
- throw new IllegalArgumentException("Unsupported image format " + format);
+ throw new IllegalArgumentException("Unsupported Image format " + format);
}
- Image.Plane[] planes = pixels.getPlanes();
- nativeSetThumbnailImage(pixels.getWidth(), pixels.getHeight(), planes[0].getBuffer(),
- planes[0].getRowStride(), planes[0].getPixelStride(), planes[1].getBuffer(),
- planes[1].getRowStride(), planes[1].getPixelStride(), planes[1].getBuffer(),
- planes[1].getRowStride(), planes[1].getPixelStride());
+ int width = pixels.getWidth();
+ int height = pixels.getHeight();
+
+ if (width > MAX_THUMBNAIL_DIMENSION || height > MAX_THUMBNAIL_DIMENSION) {
+ throw new IllegalArgumentException("Thumbnail dimensions width,height (" + width +
+ "," + height + ") too large, dimensions must be smaller than " +
+ MAX_THUMBNAIL_DIMENSION);
+ }
+
+ ByteBuffer rgbBuffer = convertToRGB(pixels);
+ nativeSetThumbnail(rgbBuffer, width, height);
return this;
}
-
/**
* Set image location metadata.
*
@@ -219,10 +225,26 @@
*
* @throws java.lang.IllegalArgumentException if the given location object doesn't
* contain enough information to set location metadata.
- * @hide
*/
public DngCreator setLocation(Location location) {
- /*TODO*/
+ if (location == null) {
+ throw new IllegalArgumentException("Null location passed to setLocation");
+ }
+ double latitude = location.getLatitude();
+ double longitude = location.getLongitude();
+ long time = location.getTime();
+
+ int[] latTag = toExifLatLong(latitude);
+ int[] longTag = toExifLatLong(longitude);
+ String latRef = latitude >= 0 ? GPS_LAT_REF_NORTH : GPS_LAT_REF_SOUTH;
+ String longRef = longitude >= 0 ? GPS_LONG_REF_EAST : GPS_LONG_REF_WEST;
+
+ String dateTag = sExifGPSDateStamp.format(time);
+ mGPSTimeStampCalendar.setTimeInMillis(time);
+ int[] timeTag = new int[] { mGPSTimeStampCalendar.get(Calendar.HOUR_OF_DAY), 1,
+ mGPSTimeStampCalendar.get(Calendar.MINUTE), 1,
+ mGPSTimeStampCalendar.get(Calendar.SECOND), 1 };
+ nativeSetGpsTags(latTag, latRef, longTag, longRef, dateTag, timeTag);
return this;
}
@@ -235,10 +257,12 @@
*
* @param description the user description string.
* @return this {@link #DngCreator} object.
- * @hide
*/
public DngCreator setDescription(String description) {
- /*TODO*/
+ if (description == null) {
+ throw new IllegalArgumentException("Null description passed to setDescription.");
+ }
+ nativeSetDescription(description);
return this;
}
@@ -268,14 +292,26 @@
* @throws java.lang.IllegalStateException if not enough metadata information has been
* set to write a well-formatted DNG file.
* @throws java.lang.IllegalArgumentException if the size passed in does not match the
- * @hide
*/
public void writeInputStream(OutputStream dngOutput, Size size, InputStream pixels, long offset)
throws IOException {
- if (dngOutput == null || pixels == null) {
- throw new NullPointerException("Null argument to writeImage");
+ if (dngOutput == null) {
+ throw new IllegalArgumentException("Null dngOutput passed to writeInputStream");
+ } else if (size == null) {
+ throw new IllegalArgumentException("Null size passed to writeInputStream");
+ } else if (pixels == null) {
+ throw new IllegalArgumentException("Null pixels passed to writeInputStream");
+ } else if (offset < 0) {
+ throw new IllegalArgumentException("Negative offset passed to writeInputStream");
}
- nativeWriteInputStream(dngOutput, pixels, offset);
+
+ int width = size.getWidth();
+ int height = size.getHeight();
+ if (width <= 0 || height <= 0) {
+ throw new IllegalArgumentException("Size with invalid width, height: (" + width + "," +
+ height + ") passed to writeInputStream");
+ }
+ nativeWriteInputStream(dngOutput, pixels, width, height, offset);
}
/**
@@ -294,6 +330,11 @@
* {@link java.lang.IllegalStateException} will be thrown.
* </p>
*
+ * <p>
+ * Any mark or limit set on this {@link ByteBuffer} is ignored, and will be cleared by this
+ * method.
+ * </p>
+ *
* @param dngOutput an {@link java.io.OutputStream} to write the DNG file to.
* @param size the {@link Size} of the image to write, in pixels.
* @param pixels an {@link java.nio.ByteBuffer} of pixel data to write.
@@ -303,14 +344,24 @@
* @throws IOException if an error was encountered in the input or output stream.
* @throws java.lang.IllegalStateException if not enough metadata information has been
* set to write a well-formatted DNG file.
- * @hide
*/
public void writeByteBuffer(OutputStream dngOutput, Size size, ByteBuffer pixels, long offset)
throws IOException {
- if (dngOutput == null || pixels == null) {
- throw new NullPointerException("Null argument to writeImage");
+ if (dngOutput == null) {
+ throw new IllegalArgumentException("Null dngOutput passed to writeByteBuffer");
+ } else if (size == null) {
+ throw new IllegalArgumentException("Null size passed to writeByteBuffer");
+ } else if (pixels == null) {
+ throw new IllegalArgumentException("Null pixels passed to writeByteBuffer");
+ } else if (offset < 0) {
+ throw new IllegalArgumentException("Negative offset passed to writeByteBuffer");
}
- nativeWriteByteBuffer(dngOutput, pixels, offset);
+
+ int width = size.getWidth();
+ int height = size.getHeight();
+
+ writeByteBuffer(width, height, pixels, dngOutput, DEFAULT_PIXEL_STRIDE,
+ width * DEFAULT_PIXEL_STRIDE, offset);
}
/**
@@ -331,8 +382,10 @@
* set to write a well-formatted DNG file.
*/
public void writeImage(OutputStream dngOutput, Image pixels) throws IOException {
- if (dngOutput == null || pixels == null) {
- throw new NullPointerException("Null argument to writeImage");
+ if (dngOutput == null) {
+ throw new IllegalArgumentException("Null dngOutput to writeImage");
+ } else if (pixels == null) {
+ throw new IllegalArgumentException("Null pixels to writeImage");
}
int format = pixels.getFormat();
@@ -341,8 +394,13 @@
}
Image.Plane[] planes = pixels.getPlanes();
- nativeWriteImage(dngOutput, pixels.getWidth(), pixels.getHeight(), planes[0].getBuffer(),
- planes[0].getRowStride(), planes[0].getPixelStride());
+ if (planes == null || planes.length <= 0) {
+ throw new IllegalArgumentException("Image with no planes passed to writeImage");
+ }
+
+ ByteBuffer buf = planes[0].getBuffer();
+ writeByteBuffer(pixels.getWidth(), pixels.getHeight(), buf, dngOutput,
+ planes[0].getPixelStride(), planes[0].getRowStride(), 0);
}
@Override
@@ -350,6 +408,11 @@
nativeDestroy();
}
+ /**
+ * Max width or height dimension for thumbnails.
+ */
+ public static final int MAX_THUMBNAIL_DIMENSION = 256; // max pixel dimension for TIFF/EP
+
@Override
protected void finalize() throws Throwable {
try {
@@ -359,13 +422,181 @@
}
}
+ private static final String GPS_LAT_REF_NORTH = "N";
+ private static final String GPS_LAT_REF_SOUTH = "S";
+ private static final String GPS_LONG_REF_EAST = "E";
+ private static final String GPS_LONG_REF_WEST = "W";
+
+ private static final String GPS_DATE_FORMAT_STR = "yyyy:MM:dd";
private static final String TIFF_DATETIME_FORMAT = "yyyy:MM:dd kk:mm:ss";
+ private static final DateFormat sExifGPSDateStamp = new SimpleDateFormat(GPS_DATE_FORMAT_STR);
private static final DateFormat sDateTimeStampFormat =
new SimpleDateFormat(TIFF_DATETIME_FORMAT);
+ private final Calendar mGPSTimeStampCalendar = Calendar
+ .getInstance(TimeZone.getTimeZone("UTC"));
static {
sDateTimeStampFormat.setTimeZone(TimeZone.getDefault());
+ sExifGPSDateStamp.setTimeZone(TimeZone.getTimeZone("UTC"));
}
+
+ private static final int DEFAULT_PIXEL_STRIDE = 2; // bytes per sample
+ private static final int BYTES_PER_RGB_PIX = 3; // byts per pixel
+
+ /**
+ * Offset, rowStride, and pixelStride are given in bytes. Height and width are given in pixels.
+ */
+ private void writeByteBuffer(int width, int height, ByteBuffer pixels, OutputStream dngOutput,
+ int pixelStride, int rowStride, long offset) throws IOException {
+ if (width <= 0 || height <= 0) {
+ throw new IllegalArgumentException("Image with invalid width, height: (" + width + "," +
+ height + ") passed to write");
+ }
+ long capacity = pixels.capacity();
+ long totalSize = rowStride * height + offset;
+ if (capacity < totalSize) {
+ throw new IllegalArgumentException("Image size " + capacity +
+ " is too small (must be larger than " + totalSize + ")");
+ }
+ int minRowStride = pixelStride * width;
+ if (minRowStride > rowStride) {
+ throw new IllegalArgumentException("Invalid image pixel stride, row byte width " +
+ minRowStride + " is too large, expecting " + rowStride);
+ }
+ pixels.clear(); // Reset mark and limit
+ nativeWriteImage(dngOutput, width, height, pixels, rowStride, pixelStride, offset,
+ pixels.isDirect());
+ pixels.clear();
+ }
+
+ /**
+ * Convert a single YUV pixel to RGB.
+ */
+ private static void yuvToRgb(byte[] yuvData, int outOffset, /*out*/byte[] rgbOut) {
+ final int COLOR_MAX = 255;
+
+ float y = yuvData[0] & 0xFF; // Y channel
+ float cb = yuvData[1] & 0xFF; // U channel
+ float cr = yuvData[2] & 0xFF; // V channel
+
+ // convert YUV -> RGB (from JFIF's "Conversion to and from RGB" section)
+ float r = y + 1.402f * (cr - 128);
+ float g = y - 0.34414f * (cb - 128) - 0.71414f * (cr - 128);
+ float b = y + 1.772f * (cb - 128);
+
+ // clamp to [0,255]
+ rgbOut[outOffset] = (byte) Math.max(0, Math.min(COLOR_MAX, r));
+ rgbOut[outOffset + 1] = (byte) Math.max(0, Math.min(COLOR_MAX, g));
+ rgbOut[outOffset + 2] = (byte) Math.max(0, Math.min(COLOR_MAX, b));
+ }
+
+ /**
+ * Convert a single {@link Color} pixel to RGB.
+ */
+ private static void colorToRgb(int color, int outOffset, /*out*/byte[] rgbOut) {
+ rgbOut[outOffset] = (byte) Color.red(color);
+ rgbOut[outOffset + 1] = (byte) Color.green(color);
+ rgbOut[outOffset + 2] = (byte) Color.blue(color);
+ // Discards Alpha
+ }
+
+ /**
+ * Generate a direct RGB {@link ByteBuffer} from a YUV420_888 {@link Image}.
+ */
+ private static ByteBuffer convertToRGB(Image yuvImage) {
+ // TODO: Optimize this with renderscript intrinsic.
+ int width = yuvImage.getWidth();
+ int height = yuvImage.getHeight();
+ ByteBuffer buf = ByteBuffer.allocateDirect(BYTES_PER_RGB_PIX * width * height);
+
+ Image.Plane yPlane = yuvImage.getPlanes()[0];
+ Image.Plane uPlane = yuvImage.getPlanes()[1];
+ Image.Plane vPlane = yuvImage.getPlanes()[2];
+
+ ByteBuffer yBuf = yPlane.getBuffer();
+ ByteBuffer uBuf = uPlane.getBuffer();
+ ByteBuffer vBuf = vPlane.getBuffer();
+
+ yBuf.rewind();
+ uBuf.rewind();
+ vBuf.rewind();
+
+ int yRowStride = yPlane.getRowStride();
+ int vRowStride = vPlane.getRowStride();
+ int uRowStride = uPlane.getRowStride();
+
+ int yPixStride = yPlane.getPixelStride();
+ int vPixStride = vPlane.getPixelStride();
+ int uPixStride = uPlane.getPixelStride();
+
+ byte[] yuvPixel = { 0, 0, 0 };
+ byte[] yFullRow = new byte[yPixStride * width];
+ byte[] uFullRow = new byte[uPixStride * width / 2];
+ byte[] vFullRow = new byte[vPixStride * width / 2];
+ byte[] finalRow = new byte[BYTES_PER_RGB_PIX * width];
+ for (int i = 0; i < height; i++) {
+ int halfH = i / 2;
+ yBuf.position(yRowStride * i);
+ yBuf.get(yFullRow);
+ uBuf.position(uRowStride * halfH);
+ uBuf.get(uFullRow);
+ vBuf.position(vRowStride * halfH);
+ vBuf.get(vFullRow);
+ for (int j = 0; j < width; j++) {
+ int halfW = j / 2;
+ yuvPixel[0] = yFullRow[yPixStride * j];
+ yuvPixel[1] = uFullRow[uPixStride * halfW];
+ yuvPixel[2] = vFullRow[vPixStride * halfW];
+ yuvToRgb(yuvPixel, j * BYTES_PER_RGB_PIX, /*out*/finalRow);
+ }
+ buf.put(finalRow);
+ }
+
+ yBuf.rewind();
+ uBuf.rewind();
+ vBuf.rewind();
+ buf.rewind();
+ return buf;
+ }
+
+ /**
+ * Generate a direct RGB {@link ByteBuffer} from a {@link Bitmap}.
+ */
+ private static ByteBuffer convertToRGB(Bitmap argbBitmap) {
+ // TODO: Optimize this.
+ int width = argbBitmap.getWidth();
+ int height = argbBitmap.getHeight();
+ ByteBuffer buf = ByteBuffer.allocateDirect(BYTES_PER_RGB_PIX * width * height);
+
+ int[] pixelRow = new int[width];
+ byte[] finalRow = new byte[BYTES_PER_RGB_PIX * width];
+ for (int i = 0; i < height; i++) {
+ argbBitmap.getPixels(pixelRow, /*offset*/0, /*stride*/width, /*x*/0, /*y*/i,
+ /*width*/width, /*height*/1);
+ for (int j = 0; j < width; j++) {
+ colorToRgb(pixelRow[j], j * BYTES_PER_RGB_PIX, /*out*/finalRow);
+ }
+ buf.put(finalRow);
+ }
+
+ buf.rewind();
+ return buf;
+ }
+
+ /**
+ * Convert coordinate to EXIF GPS tag format.
+ */
+ private static int[] toExifLatLong(double value) {
+ // convert to the format dd/1 mm/1 ssss/100
+ value = Math.abs(value);
+ int degrees = (int) value;
+ value = (value - degrees) * 60;
+ int minutes = (int) value;
+ value = (value - minutes) * 6000;
+ int seconds = (int) value;
+ return new int[] { degrees, 1, minutes, 1, seconds, 100 };
+ }
+
/**
* This field is used by native code, do not access or modify.
*/
@@ -381,24 +612,22 @@
private synchronized native void nativeSetOrientation(int orientation);
- private synchronized native void nativeSetThumbnailBitmap(Bitmap bitmap);
+ private synchronized native void nativeSetDescription(String description);
- private synchronized native void nativeSetThumbnailImage(int width, int height,
- ByteBuffer yBuffer, int yRowStride,
- int yPixStride, ByteBuffer uBuffer,
- int uRowStride, int uPixStride,
- ByteBuffer vBuffer, int vRowStride,
- int vPixStride);
+ private synchronized native void nativeSetGpsTags(int[] latTag, String latRef, int[] longTag,
+ String longRef, String dateTag,
+ int[] timeTag);
+
+ private synchronized native void nativeSetThumbnail(ByteBuffer buffer, int width, int height);
private synchronized native void nativeWriteImage(OutputStream out, int width, int height,
ByteBuffer rawBuffer, int rowStride,
- int pixStride) throws IOException;
-
- private synchronized native void nativeWriteByteBuffer(OutputStream out, ByteBuffer rawBuffer,
- long offset) throws IOException;
+ int pixStride, long offset, boolean isDirect)
+ throws IOException;
private synchronized native void nativeWriteInputStream(OutputStream out, InputStream rawStream,
- long offset) throws IOException;
+ int width, int height, long offset)
+ throws IOException;
static {
nativeClassInit();
diff --git a/core/java/android/hardware/camera2/legacy/PerfMeasurement.java b/core/java/android/hardware/camera2/legacy/PerfMeasurement.java
new file mode 100644
index 0000000..b930ec2
--- /dev/null
+++ b/core/java/android/hardware/camera2/legacy/PerfMeasurement.java
@@ -0,0 +1,309 @@
+/*
+ * 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.legacy;
+
+import android.os.SystemClock;
+import android.util.Log;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.Queue;
+
+/**
+ * GPU and CPU performance measurement for the legacy implementation.
+ *
+ * <p>Measures CPU and GPU processing duration for a set of operations, and dumps
+ * the results into a file.</p>
+ *
+ * <p>Rough usage:
+ * <pre>
+ * {@code
+ * <set up workload>
+ * <start long-running workload>
+ * mPerfMeasurement.startTimer();
+ * ...render a frame...
+ * mPerfMeasurement.stopTimer();
+ * <end workload>
+ * mPerfMeasurement.dumpPerformanceData("/sdcard/my_data.txt");
+ * }
+ * </pre>
+ * </p>
+ *
+ * <p>All calls to this object must be made within the same thread, and the same GL context.
+ * PerfMeasurement cannot be used outside of a GL context. The only exception is
+ * dumpPerformanceData, which can be called outside of a valid GL context.</p>
+ */
+class PerfMeasurement {
+ private static final String TAG = "PerfMeasurement";
+
+ public static final int DEFAULT_MAX_QUERIES = 3;
+
+ private final long mNativeContext;
+
+ private int mCompletedQueryCount = 0;
+
+ /**
+ * Values for completed measurements
+ */
+ private ArrayList<Long> mCollectedGpuDurations = new ArrayList<>();
+ private ArrayList<Long> mCollectedCpuDurations = new ArrayList<>();
+ private ArrayList<Long> mCollectedTimestamps = new ArrayList<>();
+
+ /**
+ * Values for in-progress measurements (waiting for async GPU results)
+ */
+ private Queue<Long> mTimestampQueue = new LinkedList<>();
+ private Queue<Long> mCpuDurationsQueue = new LinkedList<>();
+
+ private long mStartTimeNs;
+
+ /**
+ * The value returned by {@link #nativeGetNextGlDuration} if no new timing
+ * measurement is available since the last call.
+ */
+ private static final long NO_DURATION_YET = -1l;
+
+ /**
+ * The value returned by {@link #nativeGetNextGlDuration} if timing failed for
+ * the next timing interval
+ */
+ private static final long FAILED_TIMING = -2l;
+
+ /**
+ * Create a performance measurement object with a maximum of {@value #DEFAULT_MAX_QUERIES}
+ * in-progess queries.
+ */
+ public PerfMeasurement() {
+ mNativeContext = nativeCreateContext(DEFAULT_MAX_QUERIES);
+ }
+
+ /**
+ * Create a performance measurement object with maxQueries as the maximum number of
+ * in-progress queries.
+ *
+ * @param maxQueries maximum in-progress queries, must be larger than 0.
+ * @throws IllegalArgumentException if maxQueries is less than 1.
+ */
+ public PerfMeasurement(int maxQueries) {
+ if (maxQueries < 1) throw new IllegalArgumentException("maxQueries is less than 1");
+ mNativeContext = nativeCreateContext(maxQueries);
+ }
+
+ /**
+ * Returns true if the Gl timing methods will work, false otherwise.
+ *
+ * <p>Must be called within a valid GL context.</p>
+ */
+ public static boolean isGlTimingSupported() {
+ return nativeQuerySupport();
+ }
+
+ /**
+ * Dump collected data to file, and clear the stored data.
+ *
+ * <p>
+ * Format is a simple csv-like text file with a header,
+ * followed by a 3-column list of values in nanoseconds:
+ * <pre>
+ * timestamp gpu_duration cpu_duration
+ * <long> <long> <long>
+ * <long> <long> <long>
+ * <long> <long> <long>
+ * ....
+ * </pre>
+ * </p>
+ */
+ public void dumpPerformanceData(String path) {
+ try (BufferedWriter dump = new BufferedWriter(new FileWriter(path))) {
+ dump.write("timestamp gpu_duration cpu_duration\n");
+ for (int i = 0; i < mCollectedGpuDurations.size(); i++) {
+ dump.write(String.format("%d %d %d\n",
+ mCollectedTimestamps.get(i),
+ mCollectedGpuDurations.get(i),
+ mCollectedCpuDurations.get(i)));
+ }
+ mCollectedTimestamps.clear();
+ mCollectedGpuDurations.clear();
+ mCollectedCpuDurations.clear();
+ } catch (IOException e) {
+ Log.e(TAG, "Error writing data dump to " + path + ":" + e);
+ }
+ }
+
+ /**
+ * Start a GPU/CPU timing measurement.
+ *
+ * <p>Call before starting a rendering pass. Only one timing measurement can be active at once,
+ * so {@link #stopTimer} must be called before the next call to this method.</p>
+ *
+ * @throws IllegalStateException if the maximum number of queries are in progress already,
+ * or the method is called multiple times in a row, or there is
+ * a GPU error.
+ */
+ public void startTimer() {
+ nativeStartGlTimer(mNativeContext);
+ mStartTimeNs = SystemClock.elapsedRealtimeNanos();
+ }
+
+ /**
+ * Finish a GPU/CPU timing measurement.
+ *
+ * <p>Call after finishing all the drawing for a rendering pass. Only one timing measurement can
+ * be active at once, so {@link #startTimer} must be called before the next call to this
+ * method.</p>
+ *
+ * @throws IllegalStateException if no GL timer is currently started, or there is a GPU
+ * error.
+ */
+ public void stopTimer() {
+ // Complete CPU timing
+ long endTimeNs = SystemClock.elapsedRealtimeNanos();
+ mCpuDurationsQueue.add(endTimeNs - mStartTimeNs);
+ // Complete GL timing
+ nativeStopGlTimer(mNativeContext);
+
+ // Poll to see if GL timing results have arrived; if so
+ // store the results for a frame
+ long duration = getNextGlDuration();
+ if (duration > 0) {
+ mCollectedGpuDurations.add(duration);
+ mCollectedTimestamps.add(mTimestampQueue.isEmpty() ?
+ NO_DURATION_YET : mTimestampQueue.poll());
+ mCollectedCpuDurations.add(mCpuDurationsQueue.isEmpty() ?
+ NO_DURATION_YET : mCpuDurationsQueue.poll());
+ }
+ if (duration == FAILED_TIMING) {
+ // Discard timestamp and CPU measurement since GPU measurement failed
+ if (!mTimestampQueue.isEmpty()) {
+ mTimestampQueue.poll();
+ }
+ if (!mCpuDurationsQueue.isEmpty()) {
+ mCpuDurationsQueue.poll();
+ }
+ }
+ }
+
+ /**
+ * Add a timestamp to a timing measurement. These are queued up and matched to completed
+ * workload measurements as they become available.
+ */
+ public void addTimestamp(long timestamp) {
+ mTimestampQueue.add(timestamp);
+ }
+
+ /**
+ * Get the next available GPU timing measurement.
+ *
+ * <p>Since the GPU works asynchronously, the results of a single start/stopGlTimer measurement
+ * will only be available some time after the {@link #stopTimer} call is made. Poll this method
+ * until the result becomes available. If multiple start/endTimer measurements are made in a
+ * row, the results will be available in FIFO order.</p>
+ *
+ * @return The measured duration of the GPU workload for the next pending query, or
+ * {@link #NO_DURATION_YET} if no queries are pending or the next pending query has not
+ * yet finished, or {@link #FAILED_TIMING} if the GPU was unable to complete the
+ * measurement.
+ *
+ * @throws IllegalStateException If there is a GPU error.
+ *
+ */
+ private long getNextGlDuration() {
+ long duration = nativeGetNextGlDuration(mNativeContext);
+ if (duration > 0) {
+ mCompletedQueryCount++;
+ }
+ return duration;
+ }
+
+ /**
+ * Returns the number of measurements so far that returned a valid duration
+ * measurement.
+ */
+ public int getCompletedQueryCount() {
+ return mCompletedQueryCount;
+ }
+
+ @Override
+ protected void finalize() {
+ nativeDeleteContext(mNativeContext);
+ }
+
+ /**
+ * Create a native performance measurement context.
+ *
+ * @param maxQueryCount maximum in-progress queries; must be >= 1.
+ */
+ private static native long nativeCreateContext(int maxQueryCount);
+
+ /**
+ * Delete the native context.
+ *
+ * <p>Not safe to call more than once.</p>
+ */
+ private static native void nativeDeleteContext(long contextHandle);
+
+ /**
+ * Query whether the relevant Gl extensions are available for Gl timing
+ */
+ private static native boolean nativeQuerySupport();
+
+ /**
+ * Start a GL timing section.
+ *
+ * <p>All GL commands between this method and the next {@link #nativeEndGlTimer} will be
+ * included in the timing.</p>
+ *
+ * <p>Must be called from the same thread as calls to {@link #nativeEndGlTimer} and
+ * {@link #nativeGetNextGlDuration}.</p>
+ *
+ * @throws IllegalStateException if a GL error occurs or start is called repeatedly.
+ */
+ protected static native void nativeStartGlTimer(long contextHandle);
+
+ /**
+ * Finish a GL timing section.
+ *
+ * <p>Some time after this call returns, the time the GPU took to
+ * execute all work submitted between the latest {@link #nativeStartGlTimer} and
+ * this call, will become available from calling {@link #nativeGetNextGlDuration}.</p>
+ *
+ * <p>Must be called from the same thread as calls to {@link #nativeStartGlTimer} and
+ * {@link #nativeGetNextGlDuration}.</p>
+ *
+ * @throws IllegalStateException if a GL error occurs or stop is called before start
+ */
+ protected static native void nativeStopGlTimer(long contextHandle);
+
+ /**
+ * Get the next available GL duration measurement, in nanoseconds.
+ *
+ * <p>Must be called from the same thread as calls to {@link #nativeStartGlTimer} and
+ * {@link #nativeEndGlTimer}.</p>
+ *
+ * @return the next GL duration measurement, or {@link #NO_DURATION_YET} if
+ * no new measurement is available, or {@link #FAILED_TIMING} if timing
+ * failed for the next duration measurement.
+ * @throws IllegalStateException if a GL error occurs
+ */
+ protected static native long nativeGetNextGlDuration(long contextHandle);
+
+
+}
diff --git a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
index 9969fd2..daa64c0 100644
--- a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
+++ b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
@@ -17,6 +17,7 @@
import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
+import android.os.Environment;
import android.opengl.EGL14;
import android.opengl.EGLConfig;
import android.opengl.EGLContext;
@@ -25,10 +26,13 @@
import android.opengl.GLES11Ext;
import android.opengl.GLES20;
import android.opengl.Matrix;
+import android.text.format.Time;
import android.util.Log;
import android.util.Size;
import android.view.Surface;
+import android.os.SystemProperties;
+import java.io.File;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
@@ -126,6 +130,9 @@
private int maPositionHandle;
private int maTextureHandle;
+ private PerfMeasurement mPerfMeasurer = null;
+ private static final String LEGACY_PERF_PROPERTY = "persist.camera.legacy_perf";
+
public SurfaceTextureRenderer() {
mTriangleVertices = ByteBuffer.allocateDirect(mTriangleVerticesData.length *
FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer();
@@ -219,7 +226,6 @@
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /*offset*/ 0, /*count*/ 4);
checkGlError("glDrawArrays");
- GLES20.glFinish();
}
/**
@@ -327,9 +333,16 @@
EGL14.EGL_NONE
};
for (EGLSurfaceHolder holder : surfaces) {
- holder.eglSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, mConfigs, holder.surface,
- surfaceAttribs, 0);
- checkEglError("eglCreateWindowSurface");
+ try {
+ Size size = LegacyCameraDevice.getSurfaceSize(holder.surface);
+ holder.width = size.getWidth();
+ holder.height = size.getHeight();
+ holder.eglSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, mConfigs,
+ holder.surface, surfaceAttribs, /*offset*/ 0);
+ checkEglError("eglCreateWindowSurface");
+ } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
+ Log.w(TAG, "Surface abandoned, skipping...", e);
+ }
}
}
@@ -367,6 +380,7 @@
if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) {
EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
EGL14.EGL_NO_CONTEXT);
+ dumpGlTiming();
if (mSurfaces != null) {
for (EGLSurfaceHolder holder : mSurfaces) {
if (holder.eglSurface != null) {
@@ -418,6 +432,65 @@
}
/**
+ * Save a measurement dump to disk, in
+ * {@code /sdcard/CameraLegacy/durations_<time>_<width1>x<height1>_...txt}
+ */
+ private void dumpGlTiming() {
+ if (mPerfMeasurer == null) return;
+
+ File legacyStorageDir = new File(Environment.getExternalStorageDirectory(), "CameraLegacy");
+ if (!legacyStorageDir.exists()){
+ if (!legacyStorageDir.mkdirs()){
+ Log.e(TAG, "Failed to create directory for data dump");
+ return;
+ }
+ }
+
+ StringBuilder path = new StringBuilder(legacyStorageDir.getPath());
+ path.append(File.separator);
+ path.append("durations_");
+
+ Time now = new Time();
+ now.setToNow();
+ path.append(now.format2445());
+ path.append("_S");
+ for (EGLSurfaceHolder surface : mSurfaces) {
+ path.append(String.format("_%d_%d", surface.width, surface.height));
+ }
+ path.append("_C");
+ for (EGLSurfaceHolder surface : mConversionSurfaces) {
+ path.append(String.format("_%d_%d", surface.width, surface.height));
+ }
+ path.append(".txt");
+ mPerfMeasurer.dumpPerformanceData(path.toString());
+ }
+
+ private void setupGlTiming() {
+ if (PerfMeasurement.isGlTimingSupported()) {
+ Log.d(TAG, "Enabling GL performance measurement");
+ mPerfMeasurer = new PerfMeasurement();
+ } else {
+ Log.d(TAG, "GL performance measurement not supported on this device");
+ mPerfMeasurer = null;
+ }
+ }
+
+ private void beginGlTiming() {
+ if (mPerfMeasurer == null) return;
+ mPerfMeasurer.startTimer();
+ }
+
+ private void addGlTimestamp(long timestamp) {
+ if (mPerfMeasurer == null) return;
+ mPerfMeasurer.addTimestamp(timestamp);
+ }
+
+ private void endGlTiming() {
+ if (mPerfMeasurer == null) return;
+ mPerfMeasurer.stopTimer();
+ }
+
+ /**
* Return the surface texture to draw to - this is the texture use to when producing output
* surface buffers.
*
@@ -474,6 +547,11 @@
mConversionSurfaces.get(0).eglSurface);
initializeGLState();
mSurfaceTexture = new SurfaceTexture(getTextureId());
+
+ // Set up performance tracking if enabled
+ if (SystemProperties.getBoolean(LEGACY_PERF_PROPERTY, false)) {
+ setupGlTiming();
+ }
}
/**
@@ -494,8 +572,19 @@
}
checkGlError("before updateTexImage");
+
+ if (targetSurfaces == null) {
+ mSurfaceTexture.updateTexImage();
+ return;
+ }
+
+ beginGlTiming();
+
mSurfaceTexture.updateTexImage();
- if (targetSurfaces == null) return;
+
+ long timestamp = mSurfaceTexture.getTimestamp();
+ addGlTimestamp(timestamp);
+
List<Long> targetSurfaceIds = LegacyCameraDevice.getSurfaceIds(targetSurfaces);
for (EGLSurfaceHolder holder : mSurfaces) {
if (LegacyCameraDevice.containsSurfaceId(holder.surface, targetSurfaceIds)) {
@@ -522,6 +611,8 @@
}
}
}
+
+ endGlTiming();
}
/**
diff --git a/core/java/android/hardware/hdmi/HdmiClient.java b/core/java/android/hardware/hdmi/HdmiClient.java
index bb0f4ba..c43e21a 100644
--- a/core/java/android/hardware/hdmi/HdmiClient.java
+++ b/core/java/android/hardware/hdmi/HdmiClient.java
@@ -24,6 +24,29 @@
mService = service;
}
+ /**
+ * Send a key event to other logical device.
+ *
+ * @param keyCode key code to send. Defined in {@link android.view.KeyEvent}.
+ * @param isPressed true if this is key press event
+ */
+ public void sendKeyEvent(int keyCode, boolean isPressed) {
+ try {
+ mService.sendKeyEvent(getDeviceType(), keyCode, isPressed);
+ } catch (RemoteException e) {
+ Log.e(TAG, "queryDisplayStatus threw exception ", e);
+ }
+ }
+
+ /**
+ * Send vendor-specific command.
+ *
+ * @param targetAddress address of the target device
+ * @param params vendor-specific parameter. For <Vendor Command With ID> do not
+ * include the first 3 bytes (vendor ID).
+ * @param hasVendorId {@code true} if the command type will be <Vendor Command With ID>.
+ * {@code false} if the command will be <Vendor Command>
+ */
public void sendVendorCommand(int targetAddress, byte[] params, boolean hasVendorId) {
try {
mService.sendVendorCommand(getDeviceType(), targetAddress, params, hasVendorId);
@@ -32,6 +55,11 @@
}
}
+ /**
+ * Add a listener used to receive incoming vendor-specific command.
+ *
+ * @param listener listener object
+ */
public void addVendorCommandListener(VendorCommandListener listener) {
try {
mService.addVendorCommandListener(getListenerWrapper(listener), getDeviceType());
diff --git a/core/java/android/hardware/hdmi/IHdmiControlService.aidl b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
index 3a477fb..f3931e3 100644
--- a/core/java/android/hardware/hdmi/IHdmiControlService.aidl
+++ b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
@@ -43,7 +43,7 @@
void addDeviceEventListener(IHdmiDeviceEventListener listener);
void deviceSelect(int logicalAddress, IHdmiControlCallback callback);
void portSelect(int portId, IHdmiControlCallback callback);
- void sendKeyEvent(int keyCode, boolean isPressed);
+ void sendKeyEvent(int deviceType, int keyCode, boolean isPressed);
List<HdmiPortInfo> getPortInfo();
boolean canChangeSystemAudioMode();
boolean getSystemAudioMode();
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index 835b269..53f9fcd 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -57,6 +57,7 @@
mTransportTypes = nc.mTransportTypes;
mLinkUpBandwidthKbps = nc.mLinkUpBandwidthKbps;
mLinkDownBandwidthKbps = nc.mLinkDownBandwidthKbps;
+ mNetworkSpecifier = nc.mNetworkSpecifier;
}
}
@@ -449,7 +450,8 @@
* </p>
* The interpretation of this {@code String} is bearer specific and bearers that use
* it should document their particulars. For example, Bluetooth may use some sort of
- * device id while WiFi could used ssid and/or bssid. Cellular may use carrier spn.
+ * device id while WiFi could used SSID and/or BSSID. Cellular may use carrier SPN (name)
+ * or Subscription ID.
*
* @param networkSpecifier An {@code String} of opaque format used to specify the bearer
* specific network specifier where the bearer has a choice of
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index d0a6f08..c22c4b6 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -2076,7 +2076,7 @@
if (val != 0) hasData = true;
}
if (hasData) {
- dumpLine(pw, 0 /* uid */, category, USER_ACTIVITY_DATA, args);
+ dumpLine(pw, uid /* uid */, category, USER_ACTIVITY_DATA, args);
}
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index f48855a..07397973 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5112,6 +5112,61 @@
public static final String INSTALL_NON_MARKET_APPS = Secure.INSTALL_NON_MARKET_APPS;
/**
+ * Whether HDMI control shall be enabled. If disabled, no CEC/MHL command will be
+ * sent or processed. (0 = false, 1 = true)
+ * @hide
+ */
+ public static final String HDMI_CONTROL_ENABLED = "hdmi_control_enabled";
+
+ /**
+ * Whether HDMI system audio is enabled. If enabled, TV internal speaker is muted,
+ * and the output is redirected to AV Receiver connected via
+ * {@Global#HDMI_SYSTEM_AUDIO_OUTPUT}.
+ * @hide
+ */
+ public static final String HDMI_SYSTEM_AUDIO_ENABLED = "hdmi_system_audio_enabled";
+
+ /**
+ * Output of the audio to be used for system audio mode, as defined in AudioSystem.java.
+ * <ul>
+ * <li>DEVICE_OUT_SPDIF</li>
+ * <li>DEVICE_OUT_HDMI_ARC</li>
+ * <li>DEVICE_OUT_LINE</li>
+ * </ul>
+ * @hide
+ */
+ public static final String HDMI_SYSTEM_AUDIO_OUTPUT = "hdmi_system_audio_output";
+
+ /**
+ * Whether TV will automatically turn on upon reception of the CEC command
+ * <Text View On> or <Image View On>. (0 = false, 1 = true)
+ * @hide
+ */
+ public static final String HDMI_CONTROL_AUTO_WAKEUP_ENABLED =
+ "hdmi_control_auto_wakeup_enabled";
+
+ /**
+ * Whether TV will also turn off other CEC devices when it goes to standby mode.
+ * (0 = false, 1 = true)
+ * @hide
+ */
+ public static final String HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED =
+ "hdmi_control_auto_device_off_enabled";
+
+ /**
+ * Whether TV will switch to MHL port when a mobile device is plugged in.
+ * (0 = false, 1 = true)
+ * @hide
+ */
+ public static final String MHL_INPUT_SWITCHING_ENABLED = "mhl_input_switching_enabled";
+
+ /**
+ * Whether TV will charge the mobile device connected at MHL port. (0 = false, 1 = true)
+ * @hide
+ */
+ public static final String MHL_POWER_CHARGE_ENABLED = "mhl_power_charge_enabled";
+
+ /**
* Whether mobile data connections are allowed by the user. See
* ConnectivityManager for more info.
* @hide
diff --git a/core/java/android/widget/ActionMenuView.java b/core/java/android/widget/ActionMenuView.java
index acee592..96abf51 100644
--- a/core/java/android/widget/ActionMenuView.java
+++ b/core/java/android/widget/ActionMenuView.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.content.res.Configuration;
import android.util.AttributeSet;
+import android.view.ContextThemeWrapper;
import android.view.Gravity;
import android.view.Menu;
import android.view.MenuItem;
@@ -28,7 +29,6 @@
import com.android.internal.view.menu.ActionMenuItemView;
import com.android.internal.view.menu.MenuBuilder;
import com.android.internal.view.menu.MenuItemImpl;
-import com.android.internal.view.menu.MenuPresenter;
import com.android.internal.view.menu.MenuView;
/**
@@ -45,6 +45,12 @@
private MenuBuilder mMenu;
+ /** Context against which to inflate popup menus. */
+ private Context mPopupContext;
+
+ /** Theme resource against which to inflate popup menus. */
+ private int mPopupTheme;
+
private boolean mReserveOverflow;
private ActionMenuPresenter mPresenter;
private boolean mFormatItems;
@@ -64,9 +70,41 @@
final float density = context.getResources().getDisplayMetrics().density;
mMinCellSize = (int) (MIN_CELL_SIZE * density);
mGeneratedItemPadding = (int) (GENERATED_ITEM_PADDING * density);
+ mPopupContext = context;
+ mPopupTheme = 0;
}
- /** @hide */
+ /**
+ * Specifies the theme to use when inflating popup menus. By default, uses
+ * the same theme as the action menu view itself.
+ *
+ * @param resId theme used to inflate popup menus
+ * @see #getPopupTheme()
+ */
+ public void setPopupTheme(int resId) {
+ if (mPopupTheme != resId) {
+ mPopupTheme = resId;
+ if (resId == 0) {
+ mPopupContext = mContext;
+ } else {
+ mPopupContext = new ContextThemeWrapper(mContext, resId);
+ }
+ }
+ }
+
+ /**
+ * @return resource identifier of the theme used to inflate popup menus, or
+ * 0 if menus are inflated against the action menu view theme
+ * @see #setPopupTheme(int)
+ */
+ public int getPopupTheme() {
+ return mPopupTheme;
+ }
+
+ /**
+ * @param presenter Menu presenter used to display popup menu
+ * @hide
+ */
public void setPresenter(ActionMenuPresenter presenter) {
mPresenter = presenter;
mPresenter.setMenuView(this);
@@ -571,7 +609,7 @@
mMenu.setCallback(new MenuBuilderCallback());
mPresenter = new ActionMenuPresenter(context);
mPresenter.setCallback(new ActionMenuPresenterCallback());
- mMenu.addMenuPresenter(mPresenter);
+ mMenu.addMenuPresenter(mPresenter, mPopupContext);
mPresenter.setMenuView(this);
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index dd58ba6..fac0eb2 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -279,6 +279,7 @@
private ColorStateList mTextColor;
private ColorStateList mHintTextColor;
private ColorStateList mLinkTextColor;
+ @ViewDebug.ExportedProperty(category = "text")
private int mCurTextColor;
private int mCurHintTextColor;
private boolean mFreezesText;
@@ -2519,6 +2520,26 @@
}
/**
+ * @return the size (in scaled pixels) of thee default text size in this TextView.
+ * @hide
+ */
+ @ViewDebug.ExportedProperty(category = "text")
+ public float getScaledTextSize() {
+ return mTextPaint.getTextSize() / mTextPaint.density;
+ }
+
+ /** @hide */
+ @ViewDebug.ExportedProperty(category = "text", mapping = {
+ @ViewDebug.IntToString(from = Typeface.NORMAL, to = "NORMAL"),
+ @ViewDebug.IntToString(from = Typeface.BOLD, to = "BOLD"),
+ @ViewDebug.IntToString(from = Typeface.ITALIC, to = "ITALIC"),
+ @ViewDebug.IntToString(from = Typeface.BOLD_ITALIC, to = "BOLD_ITALIC")
+ })
+ public int getTypefaceStyle() {
+ return mTextPaint.getTypeface().getStyle();
+ }
+
+ /**
* Set the default text size to the given value, interpreted as "scaled
* pixel" units. This size is adjusted based on the current density and
* user font size preference.
diff --git a/core/java/android/widget/Toolbar.java b/core/java/android/widget/Toolbar.java
index 24950f6..712e6d0 100644
--- a/core/java/android/widget/Toolbar.java
+++ b/core/java/android/widget/Toolbar.java
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-
package android.widget;
import android.annotation.NonNull;
@@ -27,16 +26,15 @@
import android.text.Layout;
import android.text.TextUtils;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.CollapsibleActionView;
+import android.view.ContextThemeWrapper;
import android.view.Gravity;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
-import android.view.ViewDebug;
import android.view.ViewGroup;
-import android.view.Window;
+
import com.android.internal.R;
import com.android.internal.view.menu.MenuBuilder;
import com.android.internal.view.menu.MenuItemImpl;
@@ -104,6 +102,12 @@
private ImageButton mCollapseButtonView;
View mExpandedActionView;
+ /** Context against which to inflate popup menus. */
+ private Context mPopupContext;
+
+ /** Theme resource against which to inflate popup menus. */
+ private int mPopupTheme;
+
private int mTitleTextAppearance;
private int mSubtitleTextAppearance;
private int mNavButtonStyle;
@@ -230,6 +234,36 @@
setSubtitle(subtitle);
}
a.recycle();
+
+ mPopupContext = context;
+ mPopupTheme = 0;
+ }
+
+ /**
+ * Specifies the theme to use when inflating popup menus. By default, uses
+ * the same theme as the toolbar itself.
+ *
+ * @param resId theme used to inflate popup menus
+ * @see #getPopupTheme()
+ */
+ public void setPopupTheme(int resId) {
+ if (mPopupTheme != resId) {
+ mPopupTheme = resId;
+ if (resId == 0) {
+ mPopupContext = mContext;
+ } else {
+ mPopupContext = new ContextThemeWrapper(mContext, resId);
+ }
+ }
+ }
+
+ /**
+ * @return resource identifier of the theme used to inflate popup menus, or
+ * 0 if menus are inflated against the toolbar theme
+ * @see #setPopupTheme(int)
+ */
+ public int getPopupTheme() {
+ return mPopupTheme;
}
@Override
@@ -306,22 +340,21 @@
oldMenu.removeMenuPresenter(mExpandedMenuPresenter);
}
- final Context context = getContext();
-
if (mExpandedMenuPresenter == null) {
mExpandedMenuPresenter = new ExpandedActionViewMenuPresenter();
}
outerPresenter.setExpandedActionViewsExclusive(true);
if (menu != null) {
- menu.addMenuPresenter(outerPresenter);
- menu.addMenuPresenter(mExpandedMenuPresenter);
+ menu.addMenuPresenter(outerPresenter, mPopupContext);
+ menu.addMenuPresenter(mExpandedMenuPresenter, mPopupContext);
} else {
- outerPresenter.initForMenu(context, null);
- mExpandedMenuPresenter.initForMenu(context, null);
+ outerPresenter.initForMenu(mPopupContext, null);
+ mExpandedMenuPresenter.initForMenu(mPopupContext, null);
outerPresenter.updateMenuView(true);
mExpandedMenuPresenter.updateMenuView(true);
}
+ mMenuView.setPopupTheme(mPopupTheme);
mMenuView.setPresenter(outerPresenter);
mOuterActionMenuPresenter = outerPresenter;
}
@@ -768,13 +801,14 @@
mExpandedMenuPresenter = new ExpandedActionViewMenuPresenter();
}
mMenuView.setExpandedActionViewsExclusive(true);
- menu.addMenuPresenter(mExpandedMenuPresenter);
+ menu.addMenuPresenter(mExpandedMenuPresenter, mPopupContext);
}
}
private void ensureMenuView() {
if (mMenuView == null) {
mMenuView = new ActionMenuView(getContext());
+ mMenuView.setPopupTheme(mPopupTheme);
mMenuView.setOnMenuItemClickListener(mMenuViewItemClickListener);
final LayoutParams lp = generateDefaultLayoutParams();
lp.gravity = Gravity.END | (mButtonGravity & Gravity.VERTICAL_GRAVITY_MASK);
diff --git a/core/java/com/android/internal/app/ToolbarActionBar.java b/core/java/com/android/internal/app/ToolbarActionBar.java
index e8a3f0a..7af52f3 100644
--- a/core/java/com/android/internal/app/ToolbarActionBar.java
+++ b/core/java/com/android/internal/app/ToolbarActionBar.java
@@ -122,6 +122,16 @@
}
@Override
+ public void setElevation(float elevation) {
+ mToolbar.setElevation(elevation);
+ }
+
+ @Override
+ public float getElevation() {
+ return mToolbar.getElevation();
+ }
+
+ @Override
public Context getThemedContext() {
return mToolbar.getContext();
}
diff --git a/core/java/com/android/internal/app/WindowDecorActionBar.java b/core/java/com/android/internal/app/WindowDecorActionBar.java
index 7bd316f..b5ff0cc 100644
--- a/core/java/com/android/internal/app/WindowDecorActionBar.java
+++ b/core/java/com/android/internal/app/WindowDecorActionBar.java
@@ -222,6 +222,10 @@
if (a.getBoolean(R.styleable.ActionBar_hideOnContentScroll, false)) {
setHideOnContentScrollEnabled(true);
}
+ final int elevation = a.getDimensionPixelSize(R.styleable.ActionBar_elevation, 0);
+ if (elevation != 0) {
+ setElevation(elevation);
+ }
a.recycle();
}
@@ -236,6 +240,19 @@
}
}
+ @Override
+ public void setElevation(float elevation) {
+ mContainerView.setElevation(elevation);
+ if (mSplitView != null) {
+ mSplitView.setElevation(elevation);
+ }
+ }
+
+ @Override
+ public float getElevation() {
+ return mContainerView.getElevation();
+ }
+
public void onConfigurationChanged(Configuration newConfig) {
setHasEmbeddedTabs(ActionBarPolicy.get(mContext).hasEmbeddedTabs());
}
diff --git a/core/java/com/android/internal/view/menu/MenuBuilder.java b/core/java/com/android/internal/view/menu/MenuBuilder.java
index 5d7d322..e8d1ead 100644
--- a/core/java/com/android/internal/view/menu/MenuBuilder.java
+++ b/core/java/com/android/internal/view/menu/MenuBuilder.java
@@ -212,8 +212,21 @@
* @param presenter The presenter to add
*/
public void addMenuPresenter(MenuPresenter presenter) {
+ addMenuPresenter(presenter, mContext);
+ }
+
+ /**
+ * Add a presenter to this menu that uses an alternate context for
+ * inflating menu items. This will only hold a WeakReference; you do not
+ * need to explicitly remove a presenter, but you can using
+ * {@link #removeMenuPresenter(MenuPresenter)}.
+ *
+ * @param presenter The presenter to add
+ * @param menuContext The context used to inflate menu items
+ */
+ public void addMenuPresenter(MenuPresenter presenter, Context menuContext) {
mPresenters.add(new WeakReference<MenuPresenter>(presenter));
- presenter.initForMenu(mContext, this);
+ presenter.initForMenu(menuContext, this);
mIsActionItemsStale = true;
}
diff --git a/core/java/com/android/internal/view/menu/MenuPopupHelper.java b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
index 5a12893..40f58e9 100644
--- a/core/java/com/android/internal/view/menu/MenuPopupHelper.java
+++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
@@ -95,7 +95,8 @@
mAnchorView = anchorView;
- menu.addMenuPresenter(this);
+ // Present the menu using our context, not the menu builder's context.
+ menu.addMenuPresenter(this, context);
}
public void setAnchorView(View anchor) {
diff --git a/core/java/com/android/internal/view/menu/MenuPresenter.java b/core/java/com/android/internal/view/menu/MenuPresenter.java
index d913a39..f207b98 100644
--- a/core/java/com/android/internal/view/menu/MenuPresenter.java
+++ b/core/java/com/android/internal/view/menu/MenuPresenter.java
@@ -18,7 +18,6 @@
import android.content.Context;
import android.os.Parcelable;
-import android.view.Menu;
import android.view.ViewGroup;
/**
@@ -61,7 +60,7 @@
/**
* Retrieve a MenuView to display the menu specified in
- * {@link #initForMenu(Context, Menu)}.
+ * {@link #initForMenu(Context, MenuBuilder)}.
*
* @param root Intended parent of the MenuView.
* @return A freshly created MenuView.
diff --git a/core/java/com/android/internal/widget/AbsActionBarView.java b/core/java/com/android/internal/widget/AbsActionBarView.java
index 9e7ff93..850ea23 100644
--- a/core/java/com/android/internal/widget/AbsActionBarView.java
+++ b/core/java/com/android/internal/widget/AbsActionBarView.java
@@ -16,6 +16,9 @@
package com.android.internal.widget;
import com.android.internal.R;
+
+import android.util.TypedValue;
+import android.view.ContextThemeWrapper;
import android.widget.ActionMenuPresenter;
import android.widget.ActionMenuView;
@@ -32,6 +35,15 @@
import android.view.animation.DecelerateInterpolator;
public abstract class AbsActionBarView extends ViewGroup {
+ private static final TimeInterpolator sAlphaInterpolator = new DecelerateInterpolator();
+
+ private static final int FADE_DURATION = 200;
+
+ protected final VisibilityAnimListener mVisAnimListener = new VisibilityAnimListener();
+
+ /** Context against which to inflate popup menus. */
+ protected final Context mPopupContext;
+
protected ActionMenuView mMenuView;
protected ActionMenuPresenter mActionMenuPresenter;
protected ViewGroup mSplitView;
@@ -40,11 +52,6 @@
protected int mContentHeight;
protected Animator mVisibilityAnim;
- protected final VisibilityAnimListener mVisAnimListener = new VisibilityAnimListener();
-
- private static final TimeInterpolator sAlphaInterpolator = new DecelerateInterpolator();
-
- private static final int FADE_DURATION = 200;
public AbsActionBarView(Context context) {
this(context, null);
@@ -61,6 +68,14 @@
public AbsActionBarView(
Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
+
+ final TypedValue tv = new TypedValue();
+ if (context.getTheme().resolveAttribute(R.attr.actionBarPopupTheme, tv, true)
+ && tv.resourceId != 0) {
+ mPopupContext = new ContextThemeWrapper(context, tv.resourceId);
+ } else {
+ mPopupContext = context;
+ }
}
@Override
diff --git a/core/java/com/android/internal/widget/ActionBarContextView.java b/core/java/com/android/internal/widget/ActionBarContextView.java
index 6ff77a0..c7ac815 100644
--- a/core/java/com/android/internal/widget/ActionBarContextView.java
+++ b/core/java/com/android/internal/widget/ActionBarContextView.java
@@ -16,6 +16,9 @@
package com.android.internal.widget;
import com.android.internal.R;
+
+import android.util.TypedValue;
+import android.view.ContextThemeWrapper;
import android.widget.ActionMenuPresenter;
import android.widget.ActionMenuView;
import com.android.internal.view.menu.MenuBuilder;
@@ -231,7 +234,7 @@
final LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.MATCH_PARENT);
if (!mSplitActionBar) {
- menu.addMenuPresenter(mActionMenuPresenter);
+ menu.addMenuPresenter(mActionMenuPresenter, mPopupContext);
mMenuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this);
mMenuView.setBackgroundDrawable(null);
addView(mMenuView, layoutParams);
@@ -244,7 +247,7 @@
// Span the whole width
layoutParams.width = LayoutParams.MATCH_PARENT;
layoutParams.height = mContentHeight;
- menu.addMenuPresenter(mActionMenuPresenter);
+ menu.addMenuPresenter(mActionMenuPresenter, mPopupContext);
mMenuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this);
mMenuView.setBackgroundDrawable(mSplitBackground);
mSplitView.addView(mMenuView, layoutParams);
diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java
index 77559c0..e53af69 100644
--- a/core/java/com/android/internal/widget/ActionBarView.java
+++ b/core/java/com/android/internal/widget/ActionBarView.java
@@ -29,7 +29,9 @@
import android.text.Layout;
import android.text.TextUtils;
import android.util.AttributeSet;
+import android.util.TypedValue;
import android.view.CollapsibleActionView;
+import android.view.ContextThemeWrapper;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -435,11 +437,11 @@
private void configPresenters(MenuBuilder builder) {
if (builder != null) {
- builder.addMenuPresenter(mActionMenuPresenter);
- builder.addMenuPresenter(mExpandedMenuPresenter);
+ builder.addMenuPresenter(mActionMenuPresenter, mPopupContext);
+ builder.addMenuPresenter(mExpandedMenuPresenter, mPopupContext);
} else {
- mActionMenuPresenter.initForMenu(mContext, null);
- mExpandedMenuPresenter.initForMenu(mContext, null);
+ mActionMenuPresenter.initForMenu(mPopupContext, null);
+ mExpandedMenuPresenter.initForMenu(mPopupContext, null);
mActionMenuPresenter.updateMenuView(true);
mExpandedMenuPresenter.updateMenuView(true);
}
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 9b96495..ef7ef0a 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -139,6 +139,7 @@
android_hardware_Camera.cpp \
android_hardware_camera2_CameraMetadata.cpp \
android_hardware_camera2_legacy_LegacyCameraDevice.cpp \
+ android_hardware_camera2_legacy_PerfMeasurement.cpp \
android_hardware_camera2_DngCreator.cpp \
android_hardware_SensorManager.cpp \
android_hardware_SerialPort.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index f7cd8c3..9b66734 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -79,6 +79,7 @@
extern int register_android_hardware_Camera(JNIEnv *env);
extern int register_android_hardware_camera2_CameraMetadata(JNIEnv *env);
extern int register_android_hardware_camera2_legacy_LegacyCameraDevice(JNIEnv *env);
+extern int register_android_hardware_camera2_legacy_PerfMeasurement(JNIEnv *env);
extern int register_android_hardware_camera2_DngCreator(JNIEnv *env);
extern int register_android_hardware_SensorManager(JNIEnv *env);
extern int register_android_hardware_SerialPort(JNIEnv *env);
@@ -1314,6 +1315,7 @@
REG_JNI(register_android_hardware_Camera),
REG_JNI(register_android_hardware_camera2_CameraMetadata),
REG_JNI(register_android_hardware_camera2_legacy_LegacyCameraDevice),
+ REG_JNI(register_android_hardware_camera2_legacy_PerfMeasurement),
REG_JNI(register_android_hardware_camera2_DngCreator),
REG_JNI(register_android_hardware_SensorManager),
REG_JNI(register_android_hardware_SerialPort),
diff --git a/core/jni/android_hardware_camera2_DngCreator.cpp b/core/jni/android_hardware_camera2_DngCreator.cpp
index 33100bf..3a3328f 100644
--- a/core/jni/android_hardware_camera2_DngCreator.cpp
+++ b/core/jni/android_hardware_camera2_DngCreator.cpp
@@ -24,11 +24,14 @@
#include <img_utils/TiffIfd.h>
#include <img_utils/TiffWriter.h>
#include <img_utils/Output.h>
+#include <img_utils/Input.h>
+#include <img_utils/StripSource.h>
#include <utils/Log.h>
#include <utils/Errors.h>
#include <utils/StrongPointer.h>
#include <utils/RefBase.h>
+#include <utils/Vector.h>
#include <cutils/properties.h>
#include <string.h>
@@ -42,17 +45,17 @@
using namespace android;
using namespace img_utils;
-#define BAIL_IF_INVALID(expr, jnienv, tagId) \
+#define BAIL_IF_INVALID(expr, jnienv, tagId, writer) \
if ((expr) != OK) { \
jniThrowExceptionFmt(jnienv, "java/lang/IllegalArgumentException", \
- "Invalid metadata for tag %x", tagId); \
+ "Invalid metadata for tag %s (%x)", (writer)->getTagName(tagId), (tagId)); \
return; \
}
-#define BAIL_IF_EMPTY(entry, jnienv, tagId) \
+#define BAIL_IF_EMPTY(entry, jnienv, tagId, writer) \
if (entry.count == 0) { \
jniThrowExceptionFmt(jnienv, "java/lang/IllegalArgumentException", \
- "Missing metadata fields for tag %x", tagId); \
+ "Missing metadata fields for tag %s (%x)", (writer)->getTagName(tagId), (tagId)); \
return; \
}
@@ -66,15 +69,98 @@
jmethodID mWriteMethod;
} gOutputStreamClassInfo;
+static struct {
+ jmethodID mReadMethod;
+ jmethodID mSkipMethod;
+} gInputStreamClassInfo;
+
+static struct {
+ jmethodID mGetMethod;
+} gInputByteBufferClassInfo;
+
enum {
BITS_PER_SAMPLE = 16,
BYTES_PER_SAMPLE = 2,
- TIFF_IFD_0 = 0
+ BYTES_PER_RGB_PIXEL = 3,
+ BITS_PER_RGB_SAMPLE = 8,
+ BYTES_PER_RGB_SAMPLE = 1,
+ SAMPLES_PER_RGB_PIXEL = 3,
+ SAMPLES_PER_RAW_PIXEL = 1,
+ TIFF_IFD_0 = 0,
+ TIFF_IFD_SUB1 = 1,
+ TIFF_IFD_GPSINFO = 2,
};
// ----------------------------------------------------------------------------
-// This class is not intended to be used across JNI calls.
+/**
+ * Container class for the persistent native context.
+ */
+
+class NativeContext : public LightRefBase<NativeContext> {
+
+public:
+ NativeContext();
+ virtual ~NativeContext();
+
+ TiffWriter* getWriter();
+
+ uint32_t getThumbnailWidth();
+ uint32_t getThumbnailHeight();
+ const uint8_t* getThumbnail();
+
+ bool setThumbnail(const uint8_t* buffer, uint32_t width, uint32_t height);
+
+private:
+ Vector<uint8_t> mCurrentThumbnail;
+ TiffWriter mWriter;
+ uint32_t mThumbnailWidth;
+ uint32_t mThumbnailHeight;
+};
+
+NativeContext::NativeContext() : mThumbnailWidth(0), mThumbnailHeight(0) {}
+
+NativeContext::~NativeContext() {}
+
+TiffWriter* NativeContext::getWriter() {
+ return &mWriter;
+}
+
+uint32_t NativeContext::getThumbnailWidth() {
+ return mThumbnailWidth;
+}
+
+uint32_t NativeContext::getThumbnailHeight() {
+ return mThumbnailHeight;
+}
+
+const uint8_t* NativeContext::getThumbnail() {
+ return mCurrentThumbnail.array();
+}
+
+bool NativeContext::setThumbnail(const uint8_t* buffer, uint32_t width, uint32_t height) {
+ mThumbnailWidth = width;
+ mThumbnailHeight = height;
+
+ size_t size = BYTES_PER_RGB_PIXEL * width * height;
+ if (mCurrentThumbnail.resize(size) < 0) {
+ ALOGE("%s: Could not resize thumbnail buffer.", __FUNCTION__);
+ return false;
+ }
+
+ uint8_t* thumb = mCurrentThumbnail.editArray();
+ memcpy(thumb, buffer, size);
+ return true;
+}
+
+// End of NativeContext
+// ----------------------------------------------------------------------------
+
+/**
+ * Wrapper class for a Java OutputStream.
+ *
+ * This class is not intended to be used across JNI calls.
+ */
class JniOutputStream : public Output, public LightRefBase<JniOutputStream> {
public:
JniOutputStream(JNIEnv* env, jobject outStream);
@@ -82,11 +168,13 @@
virtual ~JniOutputStream();
status_t open();
+
status_t write(const uint8_t* buf, size_t offset, size_t count);
+
status_t close();
private:
enum {
- BYTE_ARRAY_LENGTH = 1024
+ BYTE_ARRAY_LENGTH = 4096
};
jobject mOutputStream;
JNIEnv* mEnv;
@@ -138,27 +226,465 @@
return OK;
}
+// End of JniOutputStream
// ----------------------------------------------------------------------------
+/**
+ * Wrapper class for a Java InputStream.
+ *
+ * This class is not intended to be used across JNI calls.
+ */
+class JniInputStream : public Input, public LightRefBase<JniInputStream> {
+public:
+ JniInputStream(JNIEnv* env, jobject inStream);
+
+ status_t open();
+
+ status_t close();
+
+ ssize_t read(uint8_t* buf, size_t offset, size_t count);
+
+ ssize_t skip(size_t count);
+
+ virtual ~JniInputStream();
+private:
+ enum {
+ BYTE_ARRAY_LENGTH = 4096
+ };
+ jobject mInStream;
+ JNIEnv* mEnv;
+ jbyteArray mByteArray;
+
+};
+
+JniInputStream::JniInputStream(JNIEnv* env, jobject inStream) : mInStream(inStream), mEnv(env) {
+ mByteArray = env->NewByteArray(BYTE_ARRAY_LENGTH);
+ if (mByteArray == NULL) {
+ jniThrowException(env, "java/lang/OutOfMemoryError", "Could not allocate byte array.");
+ }
+}
+
+JniInputStream::~JniInputStream() {
+ mEnv->DeleteLocalRef(mByteArray);
+}
+
+ssize_t JniInputStream::read(uint8_t* buf, size_t offset, size_t count) {
+
+ jint realCount = BYTE_ARRAY_LENGTH;
+ if (count < BYTE_ARRAY_LENGTH) {
+ realCount = count;
+ }
+ jint actual = mEnv->CallIntMethod(mInStream, gInputStreamClassInfo.mReadMethod, mByteArray, 0,
+ realCount);
+
+ if (actual < 0) {
+ return NOT_ENOUGH_DATA;
+ }
+
+ if (mEnv->ExceptionCheck()) {
+ return BAD_VALUE;
+ }
+
+ mEnv->GetByteArrayRegion(mByteArray, 0, actual, reinterpret_cast<jbyte*>(buf + offset));
+ if (mEnv->ExceptionCheck()) {
+ return BAD_VALUE;
+ }
+ return actual;
+}
+
+ssize_t JniInputStream::skip(size_t count) {
+ jlong actual = mEnv->CallLongMethod(mInStream, gInputStreamClassInfo.mSkipMethod,
+ static_cast<jlong>(count));
+
+ if (mEnv->ExceptionCheck()) {
+ return BAD_VALUE;
+ }
+ if (actual < 0) {
+ return NOT_ENOUGH_DATA;
+ }
+ return actual;
+}
+
+status_t JniInputStream::open() {
+ // Do nothing
+ return OK;
+}
+
+status_t JniInputStream::close() {
+ // Do nothing
+ return OK;
+}
+
+// End of JniInputStream
+// ----------------------------------------------------------------------------
+
+/**
+ * Wrapper class for a non-direct Java ByteBuffer.
+ *
+ * This class is not intended to be used across JNI calls.
+ */
+class JniInputByteBuffer : public Input, public LightRefBase<JniInputByteBuffer> {
+public:
+ JniInputByteBuffer(JNIEnv* env, jobject inBuf);
+
+ status_t open();
+
+ status_t close();
+
+ ssize_t read(uint8_t* buf, size_t offset, size_t count);
+
+ virtual ~JniInputByteBuffer();
+private:
+ enum {
+ BYTE_ARRAY_LENGTH = 4096
+ };
+ jobject mInBuf;
+ JNIEnv* mEnv;
+ jbyteArray mByteArray;
+};
+
+JniInputByteBuffer::JniInputByteBuffer(JNIEnv* env, jobject inBuf) : mInBuf(inBuf), mEnv(env) {
+ mByteArray = env->NewByteArray(BYTE_ARRAY_LENGTH);
+ if (mByteArray == NULL) {
+ jniThrowException(env, "java/lang/OutOfMemoryError", "Could not allocate byte array.");
+ }
+}
+
+JniInputByteBuffer::~JniInputByteBuffer() {
+ mEnv->DeleteLocalRef(mByteArray);
+}
+
+ssize_t JniInputByteBuffer::read(uint8_t* buf, size_t offset, size_t count) {
+ jint realCount = BYTE_ARRAY_LENGTH;
+ if (count < BYTE_ARRAY_LENGTH) {
+ realCount = count;
+ }
+
+ mEnv->CallObjectMethod(mInBuf, gInputByteBufferClassInfo.mGetMethod, mByteArray, 0,
+ realCount);
+
+ if (mEnv->ExceptionCheck()) {
+ return BAD_VALUE;
+ }
+
+ mEnv->GetByteArrayRegion(mByteArray, 0, realCount, reinterpret_cast<jbyte*>(buf + offset));
+ if (mEnv->ExceptionCheck()) {
+ return BAD_VALUE;
+ }
+ return realCount;
+}
+
+status_t JniInputByteBuffer::open() {
+ // Do nothing
+ return OK;
+}
+
+status_t JniInputByteBuffer::close() {
+ // Do nothing
+ return OK;
+}
+
+// End of JniInputByteBuffer
+// ----------------------------------------------------------------------------
+
+/**
+ * StripSource subclass for Input types.
+ *
+ * This class is not intended to be used across JNI calls.
+ */
+
+class InputStripSource : public StripSource, public LightRefBase<InputStripSource> {
+public:
+ InputStripSource(JNIEnv* env, Input& input, uint32_t ifd, uint32_t width, uint32_t height,
+ uint32_t pixStride, uint32_t rowStride, uint64_t offset, uint32_t bytesPerSample,
+ uint32_t samplesPerPixel);
+
+ virtual ~InputStripSource();
+
+ virtual status_t writeToStream(Output& stream, uint32_t count);
+
+ virtual uint32_t getIfd() const;
+protected:
+ uint32_t mIfd;
+ Input* mInput;
+ uint32_t mWidth;
+ uint32_t mHeight;
+ uint32_t mPixStride;
+ uint32_t mRowStride;
+ uint64_t mOffset;
+ JNIEnv* mEnv;
+ uint32_t mBytesPerSample;
+ uint32_t mSamplesPerPixel;
+};
+
+InputStripSource::InputStripSource(JNIEnv* env, Input& input, uint32_t ifd, uint32_t width,
+ uint32_t height, uint32_t pixStride, uint32_t rowStride, uint64_t offset,
+ uint32_t bytesPerSample, uint32_t samplesPerPixel) : mIfd(ifd), mInput(&input),
+ mWidth(width), mHeight(height), mPixStride(pixStride), mRowStride(rowStride),
+ mOffset(offset), mEnv(env), mBytesPerSample(bytesPerSample),
+ mSamplesPerPixel(samplesPerPixel) {}
+
+InputStripSource::~InputStripSource() {}
+
+status_t InputStripSource::writeToStream(Output& stream, uint32_t count) {
+ status_t err = OK;
+ uint32_t fullSize = mRowStride * mHeight;
+ jlong offset = mOffset;
+
+ if (fullSize != count) {
+ ALOGE("%s: Amount to write %u doesn't match image size %u", __FUNCTION__, count,
+ fullSize);
+ jniThrowException(mEnv, "java/lang/IllegalStateException", "Not enough data to write");
+ return BAD_VALUE;
+ }
+
+ // Skip offset
+ while (offset > 0) {
+ ssize_t skipped = mInput->skip(offset);
+ if (skipped <= 0) {
+ if (skipped == NOT_ENOUGH_DATA || skipped == 0) {
+ jniThrowExceptionFmt(mEnv, "java/io/IOException",
+ "Early EOF encountered in skip, not enough pixel data for image of size %u",
+ fullSize);
+ skipped = NOT_ENOUGH_DATA;
+ } else {
+ if (!mEnv->ExceptionCheck()) {
+ jniThrowException(mEnv, "java/io/IOException",
+ "Error encountered while skip bytes in input stream.");
+ }
+ }
+
+ return skipped;
+ }
+ offset -= skipped;
+ }
+
+ Vector<uint8_t> row;
+ if (row.resize(mRowStride) < 0) {
+ jniThrowException(mEnv, "java/lang/OutOfMemoryError", "Could not allocate row vector.");
+ return BAD_VALUE;
+ }
+
+ uint8_t* rowBytes = row.editArray();
+
+ for (uint32_t i = 0; i < mHeight; ++i) {
+ size_t rowFillAmt = 0;
+ size_t rowSize = mPixStride;
+
+ while (rowFillAmt < mRowStride) {
+ ssize_t bytesRead = mInput->read(rowBytes, rowFillAmt, rowSize);
+ if (bytesRead <= 0) {
+ if (bytesRead == NOT_ENOUGH_DATA || bytesRead == 0) {
+ jniThrowExceptionFmt(mEnv, "java/io/IOException",
+ "Early EOF encountered, not enough pixel data for image of size %u",
+ fullSize);
+ bytesRead = NOT_ENOUGH_DATA;
+ } else {
+ if (!mEnv->ExceptionCheck()) {
+ jniThrowException(mEnv, "java/io/IOException",
+ "Error encountered while reading");
+ }
+ }
+ return bytesRead;
+ }
+ rowFillAmt += bytesRead;
+ rowSize -= bytesRead;
+ }
+
+ if (mPixStride == mBytesPerSample * mSamplesPerPixel) {
+ ALOGV("%s: Using stream per-row write for strip.", __FUNCTION__);
+
+ if (stream.write(rowBytes, 0, mBytesPerSample * mSamplesPerPixel * mWidth) != OK ||
+ mEnv->ExceptionCheck()) {
+ if (!mEnv->ExceptionCheck()) {
+ jniThrowException(mEnv, "java/io/IOException", "Failed to write pixel data");
+ }
+ return BAD_VALUE;
+ }
+ } else {
+ ALOGV("%s: Using stream per-pixel write for strip.", __FUNCTION__);
+ jniThrowException(mEnv, "java/lang/IllegalStateException",
+ "Per-pixel strides are not supported for RAW16 -- pixels must be contiguous");
+ return BAD_VALUE;
+
+ // TODO: Add support for non-contiguous pixels if needed.
+ }
+ }
+ return OK;
+}
+
+uint32_t InputStripSource::getIfd() const {
+ return mIfd;
+}
+
+// End of InputStripSource
+// ----------------------------------------------------------------------------
+
+/**
+ * StripSource subclass for direct buffer types.
+ *
+ * This class is not intended to be used across JNI calls.
+ */
+
+class DirectStripSource : public StripSource, public LightRefBase<DirectStripSource> {
+public:
+ DirectStripSource(JNIEnv* env, const uint8_t* pixelBytes, uint32_t ifd, uint32_t width,
+ uint32_t height, uint32_t pixStride, uint32_t rowStride, uint64_t offset,
+ uint32_t bytesPerSample, uint32_t samplesPerPixel);
+
+ virtual ~DirectStripSource();
+
+ virtual status_t writeToStream(Output& stream, uint32_t count);
+
+ virtual uint32_t getIfd() const;
+protected:
+ uint32_t mIfd;
+ const uint8_t* mPixelBytes;
+ uint32_t mWidth;
+ uint32_t mHeight;
+ uint32_t mPixStride;
+ uint32_t mRowStride;
+ uint16_t mOffset;
+ JNIEnv* mEnv;
+ uint32_t mBytesPerSample;
+ uint32_t mSamplesPerPixel;
+};
+
+DirectStripSource::DirectStripSource(JNIEnv* env, const uint8_t* pixelBytes, uint32_t ifd,
+ uint32_t width, uint32_t height, uint32_t pixStride, uint32_t rowStride,
+ uint64_t offset, uint32_t bytesPerSample, uint32_t samplesPerPixel) : mIfd(ifd),
+ mPixelBytes(pixelBytes), mWidth(width), mHeight(height), mPixStride(pixStride),
+ mRowStride(rowStride), mOffset(offset), mEnv(env), mBytesPerSample(bytesPerSample),
+ mSamplesPerPixel(samplesPerPixel) {}
+
+DirectStripSource::~DirectStripSource() {}
+
+status_t DirectStripSource::writeToStream(Output& stream, uint32_t count) {
+ uint32_t fullSize = mRowStride * mHeight;
+
+ if (fullSize != count) {
+ ALOGE("%s: Amount to write %u doesn't match image size %u", __FUNCTION__, count,
+ fullSize);
+ jniThrowException(mEnv, "java/lang/IllegalStateException", "Not enough data to write");
+ return BAD_VALUE;
+ }
+
+ if (mPixStride == mBytesPerSample * mSamplesPerPixel
+ && mRowStride == mWidth * mBytesPerSample * mSamplesPerPixel) {
+ ALOGV("%s: Using direct single-pass write for strip.", __FUNCTION__);
+
+ if (stream.write(mPixelBytes, mOffset, fullSize) != OK || mEnv->ExceptionCheck()) {
+ if (!mEnv->ExceptionCheck()) {
+ jniThrowException(mEnv, "java/io/IOException", "Failed to write pixel data");
+ }
+ return BAD_VALUE;
+ }
+ } else if (mPixStride == mBytesPerSample * mSamplesPerPixel) {
+ ALOGV("%s: Using direct per-row write for strip.", __FUNCTION__);
+
+ for (size_t i = 0; i < mHeight; ++i) {
+ if (stream.write(mPixelBytes, mOffset + i * mRowStride, mPixStride * mWidth) != OK ||
+ mEnv->ExceptionCheck()) {
+ if (!mEnv->ExceptionCheck()) {
+ jniThrowException(mEnv, "java/io/IOException", "Failed to write pixel data");
+ }
+ return BAD_VALUE;
+ }
+ }
+ } else {
+ ALOGV("%s: Using direct per-pixel write for strip.", __FUNCTION__);
+
+ jniThrowException(mEnv, "java/lang/IllegalStateException",
+ "Per-pixel strides are not supported for RAW16 -- pixels must be contiguous");
+ return BAD_VALUE;
+
+ // TODO: Add support for non-contiguous pixels if needed.
+ }
+ return OK;
+
+}
+
+uint32_t DirectStripSource::getIfd() const {
+ return mIfd;
+}
+
+// End of DirectStripSource
+// ----------------------------------------------------------------------------
+
+static bool validateDngHeader(JNIEnv* env, TiffWriter* writer, jint width, jint height) {
+ bool hasThumbnail = writer->hasIfd(TIFF_IFD_SUB1);
+
+ // TODO: handle lens shading map, etc. conversions for other raw buffer sizes.
+ uint32_t metadataWidth = *(writer->getEntry(TAG_IMAGEWIDTH, (hasThumbnail) ? TIFF_IFD_SUB1 : TIFF_IFD_0)->getData<uint32_t>());
+ uint32_t metadataHeight = *(writer->getEntry(TAG_IMAGELENGTH, (hasThumbnail) ? TIFF_IFD_SUB1 : TIFF_IFD_0)->getData<uint32_t>());
+
+ if (width < 0 || metadataWidth != static_cast<uint32_t>(width)) {
+ jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", \
+ "Metadata width %d doesn't match image width %d", metadataWidth, width);
+ return false;
+ }
+
+ if (height < 0 || metadataHeight != static_cast<uint32_t>(height)) {
+ jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", \
+ "Metadata height %d doesn't match image height %d", metadataHeight, height);
+ return false;
+ }
+
+ return true;
+}
+
+static status_t moveEntries(TiffWriter* writer, uint32_t ifdFrom, uint32_t ifdTo,
+ const Vector<uint16_t>& entries) {
+ for (size_t i = 0; i < entries.size(); ++i) {
+ uint16_t tagId = entries[i];
+ sp<TiffEntry> entry = writer->getEntry(tagId, ifdFrom);
+ if (entry == NULL) {
+ ALOGE("%s: moveEntries failed, entry %u not found in IFD %u", __FUNCTION__, tagId,
+ ifdFrom);
+ return BAD_VALUE;
+ }
+ if (writer->addEntry(entry, ifdTo) != OK) {
+ ALOGE("%s: moveEntries failed, could not add entry %u to IFD %u", __FUNCTION__, tagId,
+ ifdFrom);
+ return BAD_VALUE;
+ }
+ writer->removeEntry(tagId, ifdFrom);
+ }
+ return OK;
+}
+
+// ----------------------------------------------------------------------------
extern "C" {
-static TiffWriter* DngCreator_getCreator(JNIEnv* env, jobject thiz) {
+static NativeContext* DngCreator_getNativeContext(JNIEnv* env, jobject thiz) {
ALOGV("%s:", __FUNCTION__);
- return reinterpret_cast<TiffWriter*>(env->GetLongField(thiz,
+ return reinterpret_cast<NativeContext*>(env->GetLongField(thiz,
gDngCreatorClassInfo.mNativeContext));
}
-static void DngCreator_setCreator(JNIEnv* env, jobject thiz, sp<TiffWriter> writer) {
+static void DngCreator_setNativeContext(JNIEnv* env, jobject thiz, sp<NativeContext> context) {
ALOGV("%s:", __FUNCTION__);
- TiffWriter* current = DngCreator_getCreator(env, thiz);
- if (writer != NULL) {
- writer->incStrong((void*) DngCreator_setCreator);
+ NativeContext* current = DngCreator_getNativeContext(env, thiz);
+
+ if (context != NULL) {
+ context->incStrong((void*) DngCreator_setNativeContext);
}
+
if (current) {
- current->decStrong((void*) DngCreator_setCreator);
+ current->decStrong((void*) DngCreator_setNativeContext);
}
+
env->SetLongField(thiz, gDngCreatorClassInfo.mNativeContext,
- reinterpret_cast<jlong>(writer.get()));
+ reinterpret_cast<jlong>(context.get()));
+}
+
+static TiffWriter* DngCreator_getCreator(JNIEnv* env, jobject thiz) {
+ ALOGV("%s:", __FUNCTION__);
+ NativeContext* current = DngCreator_getNativeContext(env, thiz);
+ if (current) {
+ return current->getWriter();
+ }
+ return NULL;
}
static void DngCreator_nativeClassInit(JNIEnv* env, jclass clazz) {
@@ -174,6 +700,19 @@
LOG_ALWAYS_FATAL_IF(outputStreamClazz == NULL, "Can't find java/io/OutputStream class");
gOutputStreamClassInfo.mWriteMethod = env->GetMethodID(outputStreamClazz, "write", "([BII)V");
LOG_ALWAYS_FATAL_IF(gOutputStreamClassInfo.mWriteMethod == NULL, "Can't find write method");
+
+ jclass inputStreamClazz = env->FindClass("java/io/InputStream");
+ LOG_ALWAYS_FATAL_IF(inputStreamClazz == NULL, "Can't find java/io/InputStream class");
+ gInputStreamClassInfo.mReadMethod = env->GetMethodID(inputStreamClazz, "read", "([BII)I");
+ LOG_ALWAYS_FATAL_IF(gInputStreamClassInfo.mReadMethod == NULL, "Can't find read method");
+ gInputStreamClassInfo.mSkipMethod = env->GetMethodID(inputStreamClazz, "skip", "(J)J");
+ LOG_ALWAYS_FATAL_IF(gInputStreamClassInfo.mSkipMethod == NULL, "Can't find skip method");
+
+ jclass inputBufferClazz = env->FindClass("java/nio/ByteBuffer");
+ LOG_ALWAYS_FATAL_IF(inputBufferClazz == NULL, "Can't find java/nio/ByteBuffer class");
+ gInputByteBufferClassInfo.mGetMethod = env->GetMethodID(inputBufferClazz, "get",
+ "([BII)Ljava/nio/ByteBuffer;");
+ LOG_ALWAYS_FATAL_IF(gInputByteBufferClassInfo.mGetMethod == NULL, "Can't find get method");
}
static void DngCreator_init(JNIEnv* env, jobject thiz, jobject characteristicsPtr,
@@ -192,7 +731,8 @@
return;
}
- sp<TiffWriter> writer = new TiffWriter();
+ sp<NativeContext> nativeContext = new NativeContext();
+ TiffWriter* writer = nativeContext->getWriter();
writer->addIfd(TIFF_IFD_0);
@@ -208,96 +748,99 @@
// TODO: Greensplit.
// TODO: Add remaining non-essential tags
+
+ // Setup main image tags
+
{
// Set orientation
uint16_t orientation = 1; // Normal
BAIL_IF_INVALID(writer->addEntry(TAG_ORIENTATION, 1, &orientation, TIFF_IFD_0), env,
- TAG_ORIENTATION);
+ TAG_ORIENTATION, writer);
}
{
// Set subfiletype
uint32_t subfileType = 0; // Main image
BAIL_IF_INVALID(writer->addEntry(TAG_NEWSUBFILETYPE, 1, &subfileType, TIFF_IFD_0), env,
- TAG_NEWSUBFILETYPE);
+ TAG_NEWSUBFILETYPE, writer);
}
{
// Set bits per sample
uint16_t bits = static_cast<uint16_t>(bitsPerSample);
BAIL_IF_INVALID(writer->addEntry(TAG_BITSPERSAMPLE, 1, &bits, TIFF_IFD_0), env,
- TAG_BITSPERSAMPLE);
+ TAG_BITSPERSAMPLE, writer);
}
{
// Set compression
uint16_t compression = 1; // None
BAIL_IF_INVALID(writer->addEntry(TAG_COMPRESSION, 1, &compression, TIFF_IFD_0), env,
- TAG_COMPRESSION);
+ TAG_COMPRESSION, writer);
}
{
// Set dimensions
camera_metadata_entry entry =
characteristics.find(ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE);
- BAIL_IF_EMPTY(entry, env, TAG_IMAGEWIDTH);
+ BAIL_IF_EMPTY(entry, env, TAG_IMAGEWIDTH, writer);
uint32_t width = static_cast<uint32_t>(entry.data.i32[2]);
uint32_t height = static_cast<uint32_t>(entry.data.i32[3]);
BAIL_IF_INVALID(writer->addEntry(TAG_IMAGEWIDTH, 1, &width, TIFF_IFD_0), env,
- TAG_IMAGEWIDTH);
+ TAG_IMAGEWIDTH, writer);
BAIL_IF_INVALID(writer->addEntry(TAG_IMAGELENGTH, 1, &height, TIFF_IFD_0), env,
- TAG_IMAGELENGTH);
+ TAG_IMAGELENGTH, writer);
imageWidth = width;
imageHeight = height;
}
{
// Set photometric interpretation
- uint16_t interpretation = 32803;
+ uint16_t interpretation = 32803; // CFA
BAIL_IF_INVALID(writer->addEntry(TAG_PHOTOMETRICINTERPRETATION, 1, &interpretation,
- TIFF_IFD_0), env, TAG_PHOTOMETRICINTERPRETATION);
+ TIFF_IFD_0), env, TAG_PHOTOMETRICINTERPRETATION, writer);
}
{
// Set blacklevel tags
camera_metadata_entry entry =
characteristics.find(ANDROID_SENSOR_BLACK_LEVEL_PATTERN);
- BAIL_IF_EMPTY(entry, env, TAG_BLACKLEVEL);
+ BAIL_IF_EMPTY(entry, env, TAG_BLACKLEVEL, writer);
const uint32_t* blackLevel = reinterpret_cast<const uint32_t*>(entry.data.i32);
BAIL_IF_INVALID(writer->addEntry(TAG_BLACKLEVEL, entry.count, blackLevel, TIFF_IFD_0), env,
- TAG_BLACKLEVEL);
+ TAG_BLACKLEVEL, writer);
uint16_t repeatDim[2] = {2, 2};
BAIL_IF_INVALID(writer->addEntry(TAG_BLACKLEVELREPEATDIM, 2, repeatDim, TIFF_IFD_0), env,
- TAG_BLACKLEVELREPEATDIM);
+ TAG_BLACKLEVELREPEATDIM, writer);
}
{
// Set samples per pixel
uint16_t samples = static_cast<uint16_t>(samplesPerPixel);
BAIL_IF_INVALID(writer->addEntry(TAG_SAMPLESPERPIXEL, 1, &samples, TIFF_IFD_0),
- env, TAG_SAMPLESPERPIXEL);
+ env, TAG_SAMPLESPERPIXEL, writer);
}
{
// Set planar configuration
uint16_t config = 1; // Chunky
BAIL_IF_INVALID(writer->addEntry(TAG_PLANARCONFIGURATION, 1, &config, TIFF_IFD_0),
- env, TAG_PLANARCONFIGURATION);
+ env, TAG_PLANARCONFIGURATION, writer);
}
{
// Set CFA pattern dimensions
uint16_t repeatDim[2] = {2, 2};
BAIL_IF_INVALID(writer->addEntry(TAG_CFAREPEATPATTERNDIM, 2, repeatDim, TIFF_IFD_0),
- env, TAG_CFAREPEATPATTERNDIM);
+ env, TAG_CFAREPEATPATTERNDIM, writer);
}
{
// Set CFA pattern
camera_metadata_entry entry =
characteristics.find(ANDROID_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT);
- BAIL_IF_EMPTY(entry, env, TAG_CFAPATTERN);
+ BAIL_IF_EMPTY(entry, env, TAG_CFAPATTERN, writer);
camera_metadata_enum_android_sensor_info_color_filter_arrangement_t cfa =
static_cast<camera_metadata_enum_android_sensor_info_color_filter_arrangement_t>(
entry.data.u8[0]);
@@ -305,28 +848,28 @@
case ANDROID_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_RGGB: {
uint8_t cfa[4] = {0, 1, 1, 2};
BAIL_IF_INVALID(writer->addEntry(TAG_CFAPATTERN, 4, cfa, TIFF_IFD_0),
- env, TAG_CFAPATTERN);
+ env, TAG_CFAPATTERN, writer);
opcodeCfaLayout = OpcodeListBuilder::CFA_RGGB;
break;
}
case ANDROID_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_GRBG: {
uint8_t cfa[4] = {1, 0, 2, 1};
BAIL_IF_INVALID(writer->addEntry(TAG_CFAPATTERN, 4, cfa, TIFF_IFD_0),
- env, TAG_CFAPATTERN);
+ env, TAG_CFAPATTERN, writer);
opcodeCfaLayout = OpcodeListBuilder::CFA_GRBG;
break;
}
case ANDROID_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_GBRG: {
uint8_t cfa[4] = {1, 2, 0, 1};
BAIL_IF_INVALID(writer->addEntry(TAG_CFAPATTERN, 4, cfa, TIFF_IFD_0),
- env, TAG_CFAPATTERN);
+ env, TAG_CFAPATTERN, writer);
opcodeCfaLayout = OpcodeListBuilder::CFA_GBRG;
break;
}
case ANDROID_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_BGGR: {
uint8_t cfa[4] = {2, 1, 1, 0};
BAIL_IF_INVALID(writer->addEntry(TAG_CFAPATTERN, 4, cfa, TIFF_IFD_0),
- env, TAG_CFAPATTERN);
+ env, TAG_CFAPATTERN, writer);
opcodeCfaLayout = OpcodeListBuilder::CFA_BGGR;
break;
}
@@ -342,21 +885,21 @@
// Set CFA plane color
uint8_t cfaPlaneColor[3] = {0, 1, 2};
BAIL_IF_INVALID(writer->addEntry(TAG_CFAPLANECOLOR, 3, cfaPlaneColor, TIFF_IFD_0),
- env, TAG_CFAPLANECOLOR);
+ env, TAG_CFAPLANECOLOR, writer);
}
{
// Set CFA layout
uint16_t cfaLayout = 1;
BAIL_IF_INVALID(writer->addEntry(TAG_CFALAYOUT, 1, &cfaLayout, TIFF_IFD_0),
- env, TAG_CFALAYOUT);
+ env, TAG_CFALAYOUT, writer);
}
{
// image description
uint8_t imageDescription = '\0'; // empty
BAIL_IF_INVALID(writer->addEntry(TAG_IMAGEDESCRIPTION, 1, &imageDescription, TIFF_IFD_0),
- env, TAG_IMAGEDESCRIPTION);
+ env, TAG_IMAGEDESCRIPTION, writer);
}
{
@@ -368,7 +911,7 @@
uint32_t count = static_cast<uint32_t>(strlen(manufacturer)) + 1;
BAIL_IF_INVALID(writer->addEntry(TAG_MAKE, count, reinterpret_cast<uint8_t*>(manufacturer),
- TIFF_IFD_0), env, TAG_MAKE);
+ TIFF_IFD_0), env, TAG_MAKE, writer);
}
{
@@ -380,23 +923,23 @@
uint32_t count = static_cast<uint32_t>(strlen(model)) + 1;
BAIL_IF_INVALID(writer->addEntry(TAG_MODEL, count, reinterpret_cast<uint8_t*>(model),
- TIFF_IFD_0), env, TAG_MODEL);
+ TIFF_IFD_0), env, TAG_MODEL, writer);
}
{
// x resolution
uint32_t xres[] = { 72, 1 }; // default 72 ppi
BAIL_IF_INVALID(writer->addEntry(TAG_XRESOLUTION, 1, xres, TIFF_IFD_0),
- env, TAG_XRESOLUTION);
+ env, TAG_XRESOLUTION, writer);
// y resolution
uint32_t yres[] = { 72, 1 }; // default 72 ppi
BAIL_IF_INVALID(writer->addEntry(TAG_YRESOLUTION, 1, yres, TIFF_IFD_0),
- env, TAG_YRESOLUTION);
+ env, TAG_YRESOLUTION, writer);
uint16_t unit = 2; // inches
BAIL_IF_INVALID(writer->addEntry(TAG_RESOLUTIONUNIT, 1, &unit, TIFF_IFD_0),
- env, TAG_RESOLUTIONUNIT);
+ env, TAG_RESOLUTIONUNIT, writer);
}
{
@@ -405,7 +948,7 @@
property_get("ro.build.fingerprint", software, "");
uint32_t count = static_cast<uint32_t>(strlen(software)) + 1;
BAIL_IF_INVALID(writer->addEntry(TAG_SOFTWARE, count, reinterpret_cast<uint8_t*>(software),
- TIFF_IFD_0), env, TAG_SOFTWARE);
+ TIFF_IFD_0), env, TAG_SOFTWARE, writer);
}
{
@@ -420,12 +963,22 @@
return;
}
- BAIL_IF_INVALID(writer->addEntry(TAG_DATETIME, DATETIME_COUNT,
- reinterpret_cast<const uint8_t*>(captureTime), TIFF_IFD_0), env, TAG_DATETIMEORIGINAL);
+ if (writer->addEntry(TAG_DATETIME, DATETIME_COUNT,
+ reinterpret_cast<const uint8_t*>(captureTime), TIFF_IFD_0) != OK) {
+ env->ReleaseStringUTFChars(formattedCaptureTime, captureTime);
+ jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
+ "Invalid metadata for tag %x", TAG_DATETIME);
+ return;
+ }
// datetime original
- BAIL_IF_INVALID(writer->addEntry(TAG_DATETIMEORIGINAL, DATETIME_COUNT,
- reinterpret_cast<const uint8_t*>(captureTime), TIFF_IFD_0), env, TAG_DATETIMEORIGINAL);
+ if (writer->addEntry(TAG_DATETIMEORIGINAL, DATETIME_COUNT,
+ reinterpret_cast<const uint8_t*>(captureTime), TIFF_IFD_0) != OK) {
+ env->ReleaseStringUTFChars(formattedCaptureTime, captureTime);
+ jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
+ "Invalid metadata for tag %x", TAG_DATETIMEORIGINAL);
+ return;
+ }
env->ReleaseStringUTFChars(formattedCaptureTime, captureTime);
}
@@ -433,21 +986,21 @@
// TIFF/EP standard id
uint8_t standardId[] = { 1, 0, 0, 0 };
BAIL_IF_INVALID(writer->addEntry(TAG_TIFFEPSTANDARDID, 4, standardId,
- TIFF_IFD_0), env, TAG_TIFFEPSTANDARDID);
+ TIFF_IFD_0), env, TAG_TIFFEPSTANDARDID, writer);
}
{
// copyright
uint8_t copyright = '\0'; // empty
BAIL_IF_INVALID(writer->addEntry(TAG_COPYRIGHT, 1, ©right,
- TIFF_IFD_0), env, TAG_COPYRIGHT);
+ TIFF_IFD_0), env, TAG_COPYRIGHT, writer);
}
{
// exposure time
camera_metadata_entry entry =
results.find(ANDROID_SENSOR_EXPOSURE_TIME);
- BAIL_IF_EMPTY(entry, env, TAG_EXPOSURETIME);
+ BAIL_IF_EMPTY(entry, env, TAG_EXPOSURETIME, writer);
int64_t exposureTime = *(entry.data.i64);
@@ -473,7 +1026,7 @@
uint32_t exposure[] = { static_cast<uint32_t>(exposureTime), denominator };
BAIL_IF_INVALID(writer->addEntry(TAG_EXPOSURETIME, 1, exposure,
- TIFF_IFD_0), env, TAG_EXPOSURETIME);
+ TIFF_IFD_0), env, TAG_EXPOSURETIME, writer);
}
@@ -481,7 +1034,7 @@
// ISO speed ratings
camera_metadata_entry entry =
results.find(ANDROID_SENSOR_SENSITIVITY);
- BAIL_IF_EMPTY(entry, env, TAG_ISOSPEEDRATINGS);
+ BAIL_IF_EMPTY(entry, env, TAG_ISOSPEEDRATINGS, writer);
int32_t tempIso = *(entry.data.i32);
if (tempIso < 0) {
@@ -497,57 +1050,57 @@
uint16_t iso = static_cast<uint16_t>(tempIso);
BAIL_IF_INVALID(writer->addEntry(TAG_ISOSPEEDRATINGS, 1, &iso,
- TIFF_IFD_0), env, TAG_ISOSPEEDRATINGS);
+ TIFF_IFD_0), env, TAG_ISOSPEEDRATINGS, writer);
}
{
// focal length
camera_metadata_entry entry =
results.find(ANDROID_LENS_FOCAL_LENGTH);
- BAIL_IF_EMPTY(entry, env, TAG_FOCALLENGTH);
+ BAIL_IF_EMPTY(entry, env, TAG_FOCALLENGTH, writer);
uint32_t focalLength[] = { static_cast<uint32_t>(*(entry.data.f) * 100), 100 };
BAIL_IF_INVALID(writer->addEntry(TAG_FOCALLENGTH, 1, focalLength,
- TIFF_IFD_0), env, TAG_FOCALLENGTH);
+ TIFF_IFD_0), env, TAG_FOCALLENGTH, writer);
}
{
// f number
camera_metadata_entry entry =
results.find(ANDROID_LENS_APERTURE);
- BAIL_IF_EMPTY(entry, env, TAG_FNUMBER);
+ BAIL_IF_EMPTY(entry, env, TAG_FNUMBER, writer);
uint32_t fnum[] = { static_cast<uint32_t>(*(entry.data.f) * 100), 100 };
BAIL_IF_INVALID(writer->addEntry(TAG_FNUMBER, 1, fnum,
- TIFF_IFD_0), env, TAG_FNUMBER);
+ TIFF_IFD_0), env, TAG_FNUMBER, writer);
}
{
// Set DNG version information
uint8_t version[4] = {1, 4, 0, 0};
BAIL_IF_INVALID(writer->addEntry(TAG_DNGVERSION, 4, version, TIFF_IFD_0),
- env, TAG_DNGVERSION);
+ env, TAG_DNGVERSION, writer);
uint8_t backwardVersion[4] = {1, 1, 0, 0};
BAIL_IF_INVALID(writer->addEntry(TAG_DNGBACKWARDVERSION, 4, backwardVersion, TIFF_IFD_0),
- env, TAG_DNGBACKWARDVERSION);
+ env, TAG_DNGBACKWARDVERSION, writer);
}
{
// Set whitelevel
camera_metadata_entry entry =
characteristics.find(ANDROID_SENSOR_INFO_WHITE_LEVEL);
- BAIL_IF_EMPTY(entry, env, TAG_WHITELEVEL);
+ BAIL_IF_EMPTY(entry, env, TAG_WHITELEVEL, writer);
uint32_t whiteLevel = static_cast<uint32_t>(entry.data.i32[0]);
BAIL_IF_INVALID(writer->addEntry(TAG_WHITELEVEL, 1, &whiteLevel, TIFF_IFD_0), env,
- TAG_WHITELEVEL);
+ TAG_WHITELEVEL, writer);
}
{
// Set default scale
uint32_t defaultScale[4] = {1, 1, 1, 1};
BAIL_IF_INVALID(writer->addEntry(TAG_DEFAULTSCALE, 2, defaultScale, TIFF_IFD_0),
- env, TAG_DEFAULTSCALE);
+ env, TAG_DEFAULTSCALE, writer);
}
bool singleIlluminant = false;
@@ -555,7 +1108,7 @@
// Set calibration illuminants
camera_metadata_entry entry1 =
characteristics.find(ANDROID_SENSOR_REFERENCE_ILLUMINANT1);
- BAIL_IF_EMPTY(entry1, env, TAG_CALIBRATIONILLUMINANT1);
+ BAIL_IF_EMPTY(entry1, env, TAG_CALIBRATIONILLUMINANT1, writer);
camera_metadata_entry entry2 =
characteristics.find(ANDROID_SENSOR_REFERENCE_ILLUMINANT2);
if (entry2.count == 0) {
@@ -564,12 +1117,12 @@
uint16_t ref1 = entry1.data.u8[0];
BAIL_IF_INVALID(writer->addEntry(TAG_CALIBRATIONILLUMINANT1, 1, &ref1,
- TIFF_IFD_0), env, TAG_CALIBRATIONILLUMINANT1);
+ TIFF_IFD_0), env, TAG_CALIBRATIONILLUMINANT1, writer);
if (!singleIlluminant) {
uint16_t ref2 = entry2.data.u8[0];
BAIL_IF_INVALID(writer->addEntry(TAG_CALIBRATIONILLUMINANT2, 1, &ref2,
- TIFF_IFD_0), env, TAG_CALIBRATIONILLUMINANT2);
+ TIFF_IFD_0), env, TAG_CALIBRATIONILLUMINANT2, writer);
}
}
@@ -577,7 +1130,7 @@
// Set color transforms
camera_metadata_entry entry1 =
characteristics.find(ANDROID_SENSOR_COLOR_TRANSFORM1);
- BAIL_IF_EMPTY(entry1, env, TAG_COLORMATRIX1);
+ BAIL_IF_EMPTY(entry1, env, TAG_COLORMATRIX1, writer);
int32_t colorTransform1[entry1.count * 2];
@@ -587,12 +1140,12 @@
colorTransform1[ctr++] = entry1.data.r[i].denominator;
}
- BAIL_IF_INVALID(writer->addEntry(TAG_COLORMATRIX1, entry1.count, colorTransform1, TIFF_IFD_0),
- env, TAG_COLORMATRIX1);
+ BAIL_IF_INVALID(writer->addEntry(TAG_COLORMATRIX1, entry1.count, colorTransform1,
+ TIFF_IFD_0), env, TAG_COLORMATRIX1, writer);
if (!singleIlluminant) {
camera_metadata_entry entry2 = characteristics.find(ANDROID_SENSOR_COLOR_TRANSFORM2);
- BAIL_IF_EMPTY(entry2, env, TAG_COLORMATRIX2);
+ BAIL_IF_EMPTY(entry2, env, TAG_COLORMATRIX2, writer);
int32_t colorTransform2[entry2.count * 2];
ctr = 0;
@@ -601,8 +1154,8 @@
colorTransform2[ctr++] = entry2.data.r[i].denominator;
}
- BAIL_IF_INVALID(writer->addEntry(TAG_COLORMATRIX2, entry2.count, colorTransform2, TIFF_IFD_0),
- env, TAG_COLORMATRIX2);
+ BAIL_IF_INVALID(writer->addEntry(TAG_COLORMATRIX2, entry2.count, colorTransform2,
+ TIFF_IFD_0), env, TAG_COLORMATRIX2, writer);
}
}
@@ -610,7 +1163,7 @@
// Set calibration transforms
camera_metadata_entry entry1 =
characteristics.find(ANDROID_SENSOR_CALIBRATION_TRANSFORM1);
- BAIL_IF_EMPTY(entry1, env, TAG_CAMERACALIBRATION1);
+ BAIL_IF_EMPTY(entry1, env, TAG_CAMERACALIBRATION1, writer);
int32_t calibrationTransform1[entry1.count * 2];
@@ -621,12 +1174,12 @@
}
BAIL_IF_INVALID(writer->addEntry(TAG_CAMERACALIBRATION1, entry1.count, calibrationTransform1,
- TIFF_IFD_0), env, TAG_CAMERACALIBRATION1);
+ TIFF_IFD_0), env, TAG_CAMERACALIBRATION1, writer);
if (!singleIlluminant) {
camera_metadata_entry entry2 =
characteristics.find(ANDROID_SENSOR_CALIBRATION_TRANSFORM2);
- BAIL_IF_EMPTY(entry2, env, TAG_CAMERACALIBRATION2);
+ BAIL_IF_EMPTY(entry2, env, TAG_CAMERACALIBRATION2, writer);
int32_t calibrationTransform2[entry2.count * 2];
ctr = 0;
@@ -636,7 +1189,7 @@
}
BAIL_IF_INVALID(writer->addEntry(TAG_CAMERACALIBRATION2, entry2.count, calibrationTransform1,
- TIFF_IFD_0), env, TAG_CAMERACALIBRATION2);
+ TIFF_IFD_0), env, TAG_CAMERACALIBRATION2, writer);
}
}
@@ -644,7 +1197,7 @@
// Set forward transforms
camera_metadata_entry entry1 =
characteristics.find(ANDROID_SENSOR_FORWARD_MATRIX1);
- BAIL_IF_EMPTY(entry1, env, TAG_FORWARDMATRIX1);
+ BAIL_IF_EMPTY(entry1, env, TAG_FORWARDMATRIX1, writer);
int32_t forwardTransform1[entry1.count * 2];
@@ -655,12 +1208,12 @@
}
BAIL_IF_INVALID(writer->addEntry(TAG_FORWARDMATRIX1, entry1.count, forwardTransform1,
- TIFF_IFD_0), env, TAG_FORWARDMATRIX1);
+ TIFF_IFD_0), env, TAG_FORWARDMATRIX1, writer);
if (!singleIlluminant) {
camera_metadata_entry entry2 =
characteristics.find(ANDROID_SENSOR_FORWARD_MATRIX2);
- BAIL_IF_EMPTY(entry2, env, TAG_FORWARDMATRIX2);
+ BAIL_IF_EMPTY(entry2, env, TAG_FORWARDMATRIX2, writer);
int32_t forwardTransform2[entry2.count * 2];
ctr = 0;
@@ -670,7 +1223,7 @@
}
BAIL_IF_INVALID(writer->addEntry(TAG_FORWARDMATRIX2, entry2.count, forwardTransform2,
- TIFF_IFD_0), env, TAG_FORWARDMATRIX2);
+ TIFF_IFD_0), env, TAG_FORWARDMATRIX2, writer);
}
}
@@ -678,7 +1231,7 @@
// Set camera neutral
camera_metadata_entry entry =
results.find(ANDROID_SENSOR_NEUTRAL_COLOR_POINT);
- BAIL_IF_EMPTY(entry, env, TAG_ASSHOTNEUTRAL);
+ BAIL_IF_EMPTY(entry, env, TAG_ASSHOTNEUTRAL, writer);
uint32_t cameraNeutral[entry.count * 2];
size_t ctr = 0;
@@ -690,23 +1243,18 @@
}
BAIL_IF_INVALID(writer->addEntry(TAG_ASSHOTNEUTRAL, entry.count, cameraNeutral,
- TIFF_IFD_0), env, TAG_ASSHOTNEUTRAL);
+ TIFF_IFD_0), env, TAG_ASSHOTNEUTRAL, writer);
}
{
// Setup data strips
// TODO: Switch to tiled implementation.
- uint32_t offset = 0;
- BAIL_IF_INVALID(writer->addEntry(TAG_STRIPOFFSETS, 1, &offset, TIFF_IFD_0), env,
- TAG_STRIPOFFSETS);
-
- BAIL_IF_INVALID(writer->addEntry(TAG_ROWSPERSTRIP, 1, &imageHeight, TIFF_IFD_0), env,
- TAG_ROWSPERSTRIP);
-
- uint32_t byteCount = imageWidth * imageHeight * bitsPerSample * samplesPerPixel /
- bitsPerByte;
- BAIL_IF_INVALID(writer->addEntry(TAG_STRIPBYTECOUNTS, 1, &byteCount, TIFF_IFD_0), env,
- TAG_STRIPBYTECOUNTS);
+ if (writer->addStrip(TIFF_IFD_0) != OK) {
+ ALOGE("%s: Could not setup strip tags.", __FUNCTION__);
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "Failed to setup strip tags.");
+ return;
+ }
}
{
@@ -717,9 +1265,9 @@
uint32_t defaultCropOrigin[] = {margin, margin};
uint32_t defaultCropSize[] = {imageWidth - margin, imageHeight - margin};
BAIL_IF_INVALID(writer->addEntry(TAG_DEFAULTCROPORIGIN, 2, defaultCropOrigin,
- TIFF_IFD_0), env, TAG_DEFAULTCROPORIGIN);
+ TIFF_IFD_0), env, TAG_DEFAULTCROPORIGIN, writer);
BAIL_IF_INVALID(writer->addEntry(TAG_DEFAULTCROPSIZE, 2, defaultCropSize,
- TIFF_IFD_0), env, TAG_DEFAULTCROPSIZE);
+ TIFF_IFD_0), env, TAG_DEFAULTCROPSIZE, writer);
}
}
@@ -742,21 +1290,26 @@
BAIL_IF_INVALID(writer->addEntry(TAG_UNIQUECAMERAMODEL, cameraModel.size() + 1,
reinterpret_cast<const uint8_t*>(cameraModel.string()), TIFF_IFD_0), env,
- TAG_UNIQUECAMERAMODEL);
+ TAG_UNIQUECAMERAMODEL, writer);
}
{
// Setup opcode List 2
camera_metadata_entry entry1 =
characteristics.find(ANDROID_LENS_INFO_SHADING_MAP_SIZE);
- BAIL_IF_EMPTY(entry1, env, TAG_OPCODELIST2);
- uint32_t lsmWidth = static_cast<uint32_t>(entry1.data.i32[0]);
- uint32_t lsmHeight = static_cast<uint32_t>(entry1.data.i32[1]);
+
+ uint32_t lsmWidth = 0;
+ uint32_t lsmHeight = 0;
+
+ if (entry1.count != 0) {
+ lsmWidth = static_cast<uint32_t>(entry1.data.i32[0]);
+ lsmHeight = static_cast<uint32_t>(entry1.data.i32[1]);
+ }
camera_metadata_entry entry2 =
results.find(ANDROID_STATISTICS_LENS_SHADING_MAP);
- BAIL_IF_EMPTY(entry2, env, TAG_OPCODELIST2);
- if (entry2.count == lsmWidth * lsmHeight * 4) {
+
+ if (entry2.count > 0 && entry2.count == lsmWidth * lsmHeight * 4) {
OpcodeListBuilder builder;
status_t err = builder.addGainMapsForMetadata(lsmWidth,
@@ -773,7 +1326,7 @@
err = builder.buildOpList(opcodeListBuf);
if (err == OK) {
BAIL_IF_INVALID(writer->addEntry(TAG_OPCODELIST2, listSize, opcodeListBuf,
- TIFF_IFD_0), env, TAG_OPCODELIST2);
+ TIFF_IFD_0), env, TAG_OPCODELIST2, writer);
} else {
ALOGE("%s: Could not build Lens shading map opcode.", __FUNCTION__);
jniThrowRuntimeException(env, "failed to construct lens shading map opcode.");
@@ -783,38 +1336,347 @@
jniThrowRuntimeException(env, "failed to add lens shading map.");
}
} else {
- ALOGW("%s: Lens shading map not present in results, skipping...", __FUNCTION__);
+ ALOGW("%s: No lens shading map found in result metadata. Image quality may be reduced.",
+ __FUNCTION__);
}
}
- DngCreator_setCreator(env, thiz, writer);
+ DngCreator_setNativeContext(env, thiz, nativeContext);
}
static void DngCreator_destroy(JNIEnv* env, jobject thiz) {
ALOGV("%s:", __FUNCTION__);
- DngCreator_setCreator(env, thiz, NULL);
+ DngCreator_setNativeContext(env, thiz, NULL);
}
-static void DngCreator_nativeSetOrientation(JNIEnv* env, jobject thiz) {
+static void DngCreator_nativeSetOrientation(JNIEnv* env, jobject thiz, jint orient) {
ALOGV("%s:", __FUNCTION__);
- jniThrowRuntimeException(env, "nativeSetOrientation is not implemented");
+
+ TiffWriter* writer = DngCreator_getCreator(env, thiz);
+ if (writer == NULL) {
+ ALOGE("%s: Failed to initialize DngCreator", __FUNCTION__);
+ jniThrowException(env, "java/lang/AssertionError",
+ "setOrientation called with uninitialized DngCreator");
+ return;
+ }
+
+ uint16_t orientation = static_cast<uint16_t>(orient);
+ BAIL_IF_INVALID(writer->addEntry(TAG_ORIENTATION, 1, &orientation, TIFF_IFD_0), env,
+ TAG_ORIENTATION, writer);
+
+ // Set main image orientation also if in a separate IFD
+ if (writer->hasIfd(TIFF_IFD_SUB1)) {
+ BAIL_IF_INVALID(writer->addEntry(TAG_ORIENTATION, 1, &orientation, TIFF_IFD_SUB1), env,
+ TAG_ORIENTATION, writer);
+ }
}
-static void DngCreator_nativeSetThumbnailBitmap(JNIEnv* env, jobject thiz, jobject bitmap) {
+static void DngCreator_nativeSetDescription(JNIEnv* env, jobject thiz, jstring description) {
ALOGV("%s:", __FUNCTION__);
- jniThrowRuntimeException(env, "nativeSetThumbnailBitmap is not implemented");
+
+ TiffWriter* writer = DngCreator_getCreator(env, thiz);
+ if (writer == NULL) {
+ ALOGE("%s: Failed to initialize DngCreator", __FUNCTION__);
+ jniThrowException(env, "java/lang/AssertionError",
+ "setDescription called with uninitialized DngCreator");
+ return;
+ }
+
+ const char* desc = env->GetStringUTFChars(description, NULL);
+ size_t len = strlen(desc) + 1;
+
+ if (writer->addEntry(TAG_IMAGEDESCRIPTION, len,
+ reinterpret_cast<const uint8_t*>(desc), TIFF_IFD_0) != OK) {
+ jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
+ "Invalid metadata for tag %x", TAG_IMAGEDESCRIPTION);
+ }
+
+ env->ReleaseStringUTFChars(description, desc);
}
-static void DngCreator_nativeSetThumbnailImage(JNIEnv* env, jobject thiz, jint width, jint height,
- jobject yBuffer, jint yRowStride, jint yPixStride, jobject uBuffer, jint uRowStride,
- jint uPixStride, jobject vBuffer, jint vRowStride, jint vPixStride) {
+static void DngCreator_nativeSetGpsTags(JNIEnv* env, jobject thiz, jintArray latTag, jstring latRef,
+ jintArray longTag, jstring longRef, jstring dateTag, jintArray timeTag) {
ALOGV("%s:", __FUNCTION__);
- jniThrowRuntimeException(env, "nativeSetThumbnailImage is not implemented");
+
+ TiffWriter* writer = DngCreator_getCreator(env, thiz);
+ if (writer == NULL) {
+ ALOGE("%s: Failed to initialize DngCreator", __FUNCTION__);
+ jniThrowException(env, "java/lang/AssertionError",
+ "setGpsTags called with uninitialized DngCreator");
+ return;
+ }
+
+ if (!writer->hasIfd(TIFF_IFD_GPSINFO)) {
+ if (writer->addSubIfd(TIFF_IFD_0, TIFF_IFD_GPSINFO, TiffWriter::GPSINFO) != OK) {
+ ALOGE("%s: Failed to add GpsInfo IFD %u to IFD %u", __FUNCTION__, TIFF_IFD_GPSINFO,
+ TIFF_IFD_0);
+ jniThrowException(env, "java/lang/IllegalStateException", "Failed to add GPSINFO");
+ return;
+ }
+ }
+
+ const jsize GPS_VALUE_LENGTH = 6;
+ jsize latLen = env->GetArrayLength(latTag);
+ jsize longLen = env->GetArrayLength(longTag);
+ jsize timeLen = env->GetArrayLength(timeTag);
+ if (latLen != GPS_VALUE_LENGTH) {
+ jniThrowException(env, "java/lang/IllegalArgumentException",
+ "invalid latitude tag length");
+ return;
+ } else if (longLen != GPS_VALUE_LENGTH) {
+ jniThrowException(env, "java/lang/IllegalArgumentException",
+ "invalid longitude tag length");
+ return;
+ } else if (timeLen != GPS_VALUE_LENGTH) {
+ jniThrowException(env, "java/lang/IllegalArgumentException",
+ "invalid time tag length");
+ return;
+ }
+
+ uint32_t latitude[GPS_VALUE_LENGTH];
+ uint32_t longitude[GPS_VALUE_LENGTH];
+ uint32_t timestamp[GPS_VALUE_LENGTH];
+
+ env->GetIntArrayRegion(latTag, 0, static_cast<jsize>(GPS_VALUE_LENGTH),
+ reinterpret_cast<jint*>(&latitude));
+ env->GetIntArrayRegion(longTag, 0, static_cast<jsize>(GPS_VALUE_LENGTH),
+ reinterpret_cast<jint*>(&longitude));
+ env->GetIntArrayRegion(timeTag, 0, static_cast<jsize>(GPS_VALUE_LENGTH),
+ reinterpret_cast<jint*>(×tamp));
+
+ const jsize GPS_REF_LENGTH = 2;
+ const jsize GPS_DATE_LENGTH = 11;
+ uint8_t latitudeRef[GPS_REF_LENGTH];
+ uint8_t longitudeRef[GPS_REF_LENGTH];
+ uint8_t date[GPS_DATE_LENGTH];
+
+ env->GetStringUTFRegion(latRef, 0, 1, reinterpret_cast<char*>(&latitudeRef));
+ latitudeRef[GPS_REF_LENGTH - 1] = '\0';
+ env->GetStringUTFRegion(longRef, 0, 1, reinterpret_cast<char*>(&longitudeRef));
+ longitudeRef[GPS_REF_LENGTH - 1] = '\0';
+
+ env->GetStringUTFRegion(dateTag, 0, GPS_DATE_LENGTH - 1, reinterpret_cast<char*>(&date));
+ date[GPS_DATE_LENGTH - 1] = '\0';
+
+ {
+ uint8_t version[] = {2, 3, 0, 0};
+ BAIL_IF_INVALID(writer->addEntry(TAG_GPSVERSIONID, 4, version,
+ TIFF_IFD_GPSINFO), env, TAG_GPSVERSIONID, writer);
+ }
+
+ {
+ BAIL_IF_INVALID(writer->addEntry(TAG_GPSLATITUDEREF, GPS_REF_LENGTH, latitudeRef,
+ TIFF_IFD_GPSINFO), env, TAG_GPSLATITUDEREF, writer);
+ }
+
+ {
+ BAIL_IF_INVALID(writer->addEntry(TAG_GPSLONGITUDEREF, GPS_REF_LENGTH, longitudeRef,
+ TIFF_IFD_GPSINFO), env, TAG_GPSLONGITUDEREF, writer);
+ }
+
+ {
+ BAIL_IF_INVALID(writer->addEntry(TAG_GPSLATITUDE, 3, latitude,
+ TIFF_IFD_GPSINFO), env, TAG_GPSLATITUDE, writer);
+ }
+
+ {
+ BAIL_IF_INVALID(writer->addEntry(TAG_GPSLONGITUDE, 3, longitude,
+ TIFF_IFD_GPSINFO), env, TAG_GPSLONGITUDE, writer);
+ }
+
+ {
+ BAIL_IF_INVALID(writer->addEntry(TAG_GPSTIMESTAMP, 3, timestamp,
+ TIFF_IFD_GPSINFO), env, TAG_GPSTIMESTAMP, writer);
+ }
+
+ {
+ BAIL_IF_INVALID(writer->addEntry(TAG_GPSDATESTAMP, GPS_DATE_LENGTH, date,
+ TIFF_IFD_GPSINFO), env, TAG_GPSDATESTAMP, writer);
+ }
}
+static void DngCreator_nativeSetThumbnail(JNIEnv* env, jobject thiz, jobject buffer, jint width,
+ jint height) {
+ ALOGV("%s:", __FUNCTION__);
+
+ NativeContext* context = DngCreator_getNativeContext(env, thiz);
+ TiffWriter* writer = DngCreator_getCreator(env, thiz);
+ if (writer == NULL || context == NULL) {
+ ALOGE("%s: Failed to initialize DngCreator", __FUNCTION__);
+ jniThrowException(env, "java/lang/AssertionError",
+ "setThumbnail called with uninitialized DngCreator");
+ return;
+ }
+
+ size_t fullSize = width * height * BYTES_PER_RGB_PIXEL;
+ jlong capacity = env->GetDirectBufferCapacity(buffer);
+ if (capacity != fullSize) {
+ jniThrowExceptionFmt(env, "java/lang/AssertionError",
+ "Invalid size %d for thumbnail, expected size was %d",
+ capacity, fullSize);
+ return;
+ }
+
+ uint8_t* pixelBytes = reinterpret_cast<uint8_t*>(env->GetDirectBufferAddress(buffer));
+ if (pixelBytes == NULL) {
+ ALOGE("%s: Could not get native ByteBuffer", __FUNCTION__);
+ jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid ByteBuffer");
+ return;
+ }
+
+ if (!writer->hasIfd(TIFF_IFD_SUB1)) {
+ if (writer->addSubIfd(TIFF_IFD_0, TIFF_IFD_SUB1) != OK) {
+ ALOGE("%s: Failed to add SubIFD %u to IFD %u", __FUNCTION__, TIFF_IFD_SUB1,
+ TIFF_IFD_0);
+ jniThrowException(env, "java/lang/IllegalStateException", "Failed to add SubIFD");
+ return;
+ }
+
+ Vector<uint16_t> tagsToMove;
+ tagsToMove.add(TAG_ORIENTATION);
+ tagsToMove.add(TAG_NEWSUBFILETYPE);
+ tagsToMove.add(TAG_BITSPERSAMPLE);
+ tagsToMove.add(TAG_COMPRESSION);
+ tagsToMove.add(TAG_IMAGEWIDTH);
+ tagsToMove.add(TAG_IMAGELENGTH);
+ tagsToMove.add(TAG_PHOTOMETRICINTERPRETATION);
+ tagsToMove.add(TAG_BLACKLEVEL);
+ tagsToMove.add(TAG_BLACKLEVELREPEATDIM);
+ tagsToMove.add(TAG_SAMPLESPERPIXEL);
+ tagsToMove.add(TAG_PLANARCONFIGURATION);
+ tagsToMove.add(TAG_CFAREPEATPATTERNDIM);
+ tagsToMove.add(TAG_CFAPATTERN);
+ tagsToMove.add(TAG_CFAPLANECOLOR);
+ tagsToMove.add(TAG_CFALAYOUT);
+ tagsToMove.add(TAG_XRESOLUTION);
+ tagsToMove.add(TAG_YRESOLUTION);
+ tagsToMove.add(TAG_RESOLUTIONUNIT);
+ tagsToMove.add(TAG_WHITELEVEL);
+ tagsToMove.add(TAG_DEFAULTSCALE);
+ tagsToMove.add(TAG_ROWSPERSTRIP);
+ tagsToMove.add(TAG_STRIPBYTECOUNTS);
+ tagsToMove.add(TAG_STRIPOFFSETS);
+ tagsToMove.add(TAG_DEFAULTCROPORIGIN);
+ tagsToMove.add(TAG_DEFAULTCROPSIZE);
+ tagsToMove.add(TAG_OPCODELIST2);
+
+ if (moveEntries(writer, TIFF_IFD_0, TIFF_IFD_SUB1, tagsToMove) != OK) {
+ jniThrowException(env, "java/lang/IllegalStateException", "Failed to move entries");
+ return;
+ }
+
+ // Make sure both IFDs get the same orientation tag
+ sp<TiffEntry> orientEntry = writer->getEntry(TAG_ORIENTATION, TIFF_IFD_SUB1);
+ if (orientEntry != NULL) {
+ writer->addEntry(orientEntry, TIFF_IFD_0);
+ }
+ }
+
+ // Setup thumbnail tags
+
+ {
+ // Set photometric interpretation
+ uint16_t interpretation = 2; // RGB
+ BAIL_IF_INVALID(writer->addEntry(TAG_PHOTOMETRICINTERPRETATION, 1, &interpretation,
+ TIFF_IFD_0), env, TAG_PHOTOMETRICINTERPRETATION, writer);
+ }
+
+ {
+ // Set planar configuration
+ uint16_t config = 1; // Chunky
+ BAIL_IF_INVALID(writer->addEntry(TAG_PLANARCONFIGURATION, 1, &config, TIFF_IFD_0),
+ env, TAG_PLANARCONFIGURATION, writer);
+ }
+
+ {
+ // Set samples per pixel
+ uint16_t samples = SAMPLES_PER_RGB_PIXEL;
+ BAIL_IF_INVALID(writer->addEntry(TAG_SAMPLESPERPIXEL, 1, &samples, TIFF_IFD_0),
+ env, TAG_SAMPLESPERPIXEL, writer);
+ }
+
+ {
+ // Set bits per sample
+ uint16_t bits = BITS_PER_RGB_SAMPLE;
+ BAIL_IF_INVALID(writer->addEntry(TAG_BITSPERSAMPLE, 1, &bits, TIFF_IFD_0), env,
+ TAG_BITSPERSAMPLE, writer);
+ }
+
+ {
+ // Set subfiletype
+ uint32_t subfileType = 1; // Thumbnail image
+ BAIL_IF_INVALID(writer->addEntry(TAG_NEWSUBFILETYPE, 1, &subfileType, TIFF_IFD_0), env,
+ TAG_NEWSUBFILETYPE, writer);
+ }
+
+ {
+ // Set compression
+ uint16_t compression = 1; // None
+ BAIL_IF_INVALID(writer->addEntry(TAG_COMPRESSION, 1, &compression, TIFF_IFD_0), env,
+ TAG_COMPRESSION, writer);
+ }
+
+ {
+ // Set dimensions
+ uint32_t uWidth = static_cast<uint32_t>(width);
+ uint32_t uHeight = static_cast<uint32_t>(height);
+ BAIL_IF_INVALID(writer->addEntry(TAG_IMAGEWIDTH, 1, &uWidth, TIFF_IFD_0), env,
+ TAG_IMAGEWIDTH, writer);
+ BAIL_IF_INVALID(writer->addEntry(TAG_IMAGELENGTH, 1, &uHeight, TIFF_IFD_0), env,
+ TAG_IMAGELENGTH, writer);
+ }
+
+ {
+ // x resolution
+ uint32_t xres[] = { 72, 1 }; // default 72 ppi
+ BAIL_IF_INVALID(writer->addEntry(TAG_XRESOLUTION, 1, xres, TIFF_IFD_0),
+ env, TAG_XRESOLUTION, writer);
+
+ // y resolution
+ uint32_t yres[] = { 72, 1 }; // default 72 ppi
+ BAIL_IF_INVALID(writer->addEntry(TAG_YRESOLUTION, 1, yres, TIFF_IFD_0),
+ env, TAG_YRESOLUTION, writer);
+
+ uint16_t unit = 2; // inches
+ BAIL_IF_INVALID(writer->addEntry(TAG_RESOLUTIONUNIT, 1, &unit, TIFF_IFD_0),
+ env, TAG_RESOLUTIONUNIT, writer);
+ }
+
+ {
+ // Setup data strips
+ if (writer->addStrip(TIFF_IFD_0) != OK) {
+ ALOGE("%s: Could not setup thumbnail strip tags.", __FUNCTION__);
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "Failed to setup thumbnail strip tags.");
+ return;
+ }
+ if (writer->addStrip(TIFF_IFD_SUB1) != OK) {
+ ALOGE("%s: Could not main image strip tags.", __FUNCTION__);
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "Failed to setup main image strip tags.");
+ return;
+ }
+ }
+
+ if (!context->setThumbnail(pixelBytes, width, height)) {
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "Failed to set thumbnail.");
+ return;
+ }
+}
+
+// TODO: Refactor out common preamble for the two nativeWrite methods.
static void DngCreator_nativeWriteImage(JNIEnv* env, jobject thiz, jobject outStream, jint width,
- jint height, jobject inBuffer, jint rowStride, jint pixStride) {
+ jint height, jobject inBuffer, jint rowStride, jint pixStride, jlong offset,
+ jboolean isDirect) {
ALOGV("%s:", __FUNCTION__);
+ ALOGV("%s: nativeWriteImage called with: width=%d, height=%d, rowStride=%d, pixStride=%d,"
+ " offset=%lld", __FUNCTION__, width, height, rowStride, pixStride, offset);
+ uint32_t rStride = static_cast<uint32_t>(rowStride);
+ uint32_t pStride = static_cast<uint32_t>(pixStride);
+ uint32_t uWidth = static_cast<uint32_t>(width);
+ uint32_t uHeight = static_cast<uint32_t>(height);
+ uint64_t uOffset = static_cast<uint64_t>(offset);
sp<JniOutputStream> out = new JniOutputStream(env, outStream);
if(env->ExceptionCheck()) {
@@ -822,99 +1684,157 @@
return;
}
- uint8_t* pixelBytes = reinterpret_cast<uint8_t*>(env->GetDirectBufferAddress(inBuffer));
- if (pixelBytes == NULL) {
- ALOGE("%s: Could not get native byte buffer", __FUNCTION__);
- jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid bytebuffer");
- return;
- }
-
TiffWriter* writer = DngCreator_getCreator(env, thiz);
- if (writer == NULL) {
+ NativeContext* context = DngCreator_getNativeContext(env, thiz);
+ if (writer == NULL || context == NULL) {
ALOGE("%s: Failed to initialize DngCreator", __FUNCTION__);
jniThrowException(env, "java/lang/AssertionError",
"Write called with uninitialized DngCreator");
return;
}
- // TODO: handle lens shading map, etc. conversions for other raw buffer sizes.
- uint32_t metadataWidth = *(writer->getEntry(TAG_IMAGEWIDTH, TIFF_IFD_0)->getData<uint32_t>());
- uint32_t metadataHeight = *(writer->getEntry(TAG_IMAGELENGTH, TIFF_IFD_0)->getData<uint32_t>());
- if (metadataWidth != width) {
- jniThrowExceptionFmt(env, "java/lang/IllegalStateException", \
- "Metadata width %d doesn't match image width %d", metadataWidth, width);
+
+ // Validate DNG header
+ if (!validateDngHeader(env, writer, width, height)) {
return;
}
- if (metadataHeight != height) {
- jniThrowExceptionFmt(env, "java/lang/IllegalStateException", \
- "Metadata height %d doesn't match image height %d", metadataHeight, height);
- return;
+ sp<JniInputByteBuffer> inBuf;
+ Vector<StripSource*> sources;
+ sp<DirectStripSource> thumbnailSource;
+ uint32_t targetIfd = TIFF_IFD_0;
+
+ bool hasThumbnail = writer->hasIfd(TIFF_IFD_SUB1);
+
+ if (hasThumbnail) {
+ ALOGV("%s: Adding thumbnail strip sources.", __FUNCTION__);
+ uint32_t bytesPerPixel = SAMPLES_PER_RGB_PIXEL * BYTES_PER_RGB_SAMPLE;
+ uint32_t thumbWidth = context->getThumbnailWidth();
+ thumbnailSource = new DirectStripSource(env, context->getThumbnail(), TIFF_IFD_0,
+ thumbWidth, context->getThumbnailHeight(), bytesPerPixel,
+ bytesPerPixel * thumbWidth, /*offset*/0, BYTES_PER_RGB_SAMPLE,
+ SAMPLES_PER_RGB_PIXEL);
+ sources.add(thumbnailSource.get());
+ targetIfd = TIFF_IFD_SUB1;
}
- uint32_t stripOffset = writer->getTotalSize();
-
- BAIL_IF_INVALID(writer->addEntry(TAG_STRIPOFFSETS, 1, &stripOffset, TIFF_IFD_0), env,
- TAG_STRIPOFFSETS);
-
- if (writer->write(out.get()) != OK) {
- if (!env->ExceptionCheck()) {
- jniThrowException(env, "java/io/IOException", "Failed to write metadata");
+ if (isDirect) {
+ size_t fullSize = rStride * uHeight;
+ jlong capacity = env->GetDirectBufferCapacity(inBuffer);
+ if (capacity < 0 || fullSize + uOffset > static_cast<uint64_t>(capacity)) {
+ jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
+ "Invalid size %d for Image, size given in metadata is %d at current stride",
+ capacity, fullSize);
+ return;
}
- return;
- }
- size_t fullSize = rowStride * height;
- jlong capacity = env->GetDirectBufferCapacity(inBuffer);
- if (capacity < 0 || fullSize > capacity) {
- jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
- "Invalid size %d for Image, size given in metadata is %d at current stride",
- capacity, fullSize);
- return;
- }
+ uint8_t* pixelBytes = reinterpret_cast<uint8_t*>(env->GetDirectBufferAddress(inBuffer));
+ if (pixelBytes == NULL) {
+ ALOGE("%s: Could not get native ByteBuffer", __FUNCTION__);
+ jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid ByteBuffer");
+ return;
+ }
- if (pixStride == BYTES_PER_SAMPLE && rowStride == width * BYTES_PER_SAMPLE) {
- if (out->write(pixelBytes, 0, fullSize) != OK || env->ExceptionCheck()) {
+ ALOGV("%s: Using direct-type strip source.", __FUNCTION__);
+ DirectStripSource stripSource(env, pixelBytes, targetIfd, uWidth, uHeight, pStride,
+ rStride, uOffset, BYTES_PER_SAMPLE, SAMPLES_PER_RAW_PIXEL);
+ sources.add(&stripSource);
+
+ status_t ret = OK;
+ if ((ret = writer->write(out.get(), sources.editArray(), sources.size())) != OK) {
+ ALOGE("%s: write failed with error %d.", __FUNCTION__, ret);
if (!env->ExceptionCheck()) {
- jniThrowException(env, "java/io/IOException", "Failed to write pixel data");
+ jniThrowExceptionFmt(env, "java/io/IOException",
+ "Encountered error %d while writing file.", ret);
}
return;
}
- } else if (pixStride == BYTES_PER_SAMPLE) {
- for (size_t i = 0; i < height; ++i) {
- if (out->write(pixelBytes, i * rowStride, pixStride * width) != OK ||
- env->ExceptionCheck()) {
- if (!env->ExceptionCheck()) {
- jniThrowException(env, "java/io/IOException", "Failed to write pixel data");
- }
- return;
- }
- }
} else {
- for (size_t i = 0; i < height; ++i) {
- for (size_t j = 0; j < width; ++j) {
- if (out->write(pixelBytes, i * rowStride + j * pixStride,
- BYTES_PER_SAMPLE) != OK || !env->ExceptionCheck()) {
- if (env->ExceptionCheck()) {
- jniThrowException(env, "java/io/IOException", "Failed to write pixel data");
- }
- return;
- }
+ inBuf = new JniInputByteBuffer(env, inBuffer);
+
+ ALOGV("%s: Using input-type strip source.", __FUNCTION__);
+ InputStripSource stripSource(env, *inBuf, targetIfd, uWidth, uHeight, pStride,
+ rStride, uOffset, BYTES_PER_SAMPLE, SAMPLES_PER_RAW_PIXEL);
+ sources.add(&stripSource);
+
+ status_t ret = OK;
+ if ((ret = writer->write(out.get(), sources.editArray(), sources.size())) != OK) {
+ ALOGE("%s: write failed with error %d.", __FUNCTION__, ret);
+ if (!env->ExceptionCheck()) {
+ jniThrowExceptionFmt(env, "java/io/IOException",
+ "Encountered error %d while writing file.", ret);
}
+ return;
}
}
-
-}
-
-static void DngCreator_nativeWriteByteBuffer(JNIEnv* env, jobject thiz, jobject outStream,
- jobject rawBuffer, jlong offset) {
- ALOGV("%s:", __FUNCTION__);
- jniThrowRuntimeException(env, "nativeWriteByteBuffer is not implemented.");
}
static void DngCreator_nativeWriteInputStream(JNIEnv* env, jobject thiz, jobject outStream,
- jobject inStream, jlong offset) {
+ jobject inStream, jint width, jint height, jlong offset) {
ALOGV("%s:", __FUNCTION__);
- jniThrowRuntimeException(env, "nativeWriteInputStream is not implemented.");
+
+ uint32_t rowStride = width * BYTES_PER_SAMPLE;
+ uint32_t pixStride = BYTES_PER_SAMPLE;
+ uint32_t uWidth = static_cast<uint32_t>(width);
+ uint32_t uHeight = static_cast<uint32_t>(height);
+ uint64_t uOffset = static_cast<uint32_t>(offset);
+
+ ALOGV("%s: nativeWriteInputStream called with: width=%d, height=%d, rowStride=%u,"
+ "pixStride=%u, offset=%lld", __FUNCTION__, width, height, rowStride, pixStride,
+ offset);
+
+ sp<JniOutputStream> out = new JniOutputStream(env, outStream);
+ if(env->ExceptionCheck()) {
+ ALOGE("%s: Could not allocate buffers for output stream", __FUNCTION__);
+ return;
+ }
+
+ TiffWriter* writer = DngCreator_getCreator(env, thiz);
+ NativeContext* context = DngCreator_getNativeContext(env, thiz);
+ if (writer == NULL || context == NULL) {
+ ALOGE("%s: Failed to initialize DngCreator", __FUNCTION__);
+ jniThrowException(env, "java/lang/AssertionError",
+ "Write called with uninitialized DngCreator");
+ return;
+ }
+
+ // Validate DNG header
+ if (!validateDngHeader(env, writer, width, height)) {
+ return;
+ }
+
+ sp<DirectStripSource> thumbnailSource;
+ uint32_t targetIfd = TIFF_IFD_0;
+ bool hasThumbnail = writer->hasIfd(TIFF_IFD_SUB1);
+ Vector<StripSource*> sources;
+
+ if (hasThumbnail) {
+ ALOGV("%s: Adding thumbnail strip sources.", __FUNCTION__);
+ uint32_t bytesPerPixel = SAMPLES_PER_RGB_PIXEL * BYTES_PER_RGB_SAMPLE;
+ uint32_t width = context->getThumbnailWidth();
+ thumbnailSource = new DirectStripSource(env, context->getThumbnail(), TIFF_IFD_0,
+ width, context->getThumbnailHeight(), bytesPerPixel,
+ bytesPerPixel * width, /*offset*/0, BYTES_PER_RGB_SAMPLE,
+ SAMPLES_PER_RGB_PIXEL);
+ sources.add(thumbnailSource.get());
+ targetIfd = TIFF_IFD_SUB1;
+ }
+
+ sp<JniInputStream> in = new JniInputStream(env, inStream);
+
+ ALOGV("%s: Using input-type strip source.", __FUNCTION__);
+ InputStripSource stripSource(env, *in, targetIfd, uWidth, uHeight, pixStride,
+ rowStride, uOffset, BYTES_PER_SAMPLE, SAMPLES_PER_RAW_PIXEL);
+ sources.add(&stripSource);
+
+ status_t ret = OK;
+ if ((ret = writer->write(out.get(), sources.editArray(), sources.size())) != OK) {
+ ALOGE("%s: write failed with error %d.", __FUNCTION__, ret);
+ if (!env->ExceptionCheck()) {
+ jniThrowExceptionFmt(env, "java/io/IOException",
+ "Encountered error %d while writing file.", ret);
+ }
+ return;
+ }
}
} /*extern "C" */
@@ -926,16 +1846,13 @@
(void*) DngCreator_init},
{"nativeDestroy", "()V", (void*) DngCreator_destroy},
{"nativeSetOrientation", "(I)V", (void*) DngCreator_nativeSetOrientation},
- {"nativeSetThumbnailBitmap","(Landroid/graphics/Bitmap;)V",
- (void*) DngCreator_nativeSetThumbnailBitmap},
- {"nativeSetThumbnailImage",
- "(IILjava/nio/ByteBuffer;IILjava/nio/ByteBuffer;IILjava/nio/ByteBuffer;II)V",
- (void*) DngCreator_nativeSetThumbnailImage},
- {"nativeWriteImage", "(Ljava/io/OutputStream;IILjava/nio/ByteBuffer;II)V",
+ {"nativeSetDescription", "(Ljava/lang/String;)V", (void*) DngCreator_nativeSetDescription},
+ {"nativeSetGpsTags", "([ILjava/lang/String;[ILjava/lang/String;Ljava/lang/String;[I)V",
+ (void*) DngCreator_nativeSetGpsTags},
+ {"nativeSetThumbnail","(Ljava/nio/ByteBuffer;II)V", (void*) DngCreator_nativeSetThumbnail},
+ {"nativeWriteImage", "(Ljava/io/OutputStream;IILjava/nio/ByteBuffer;IIJZ)V",
(void*) DngCreator_nativeWriteImage},
- {"nativeWriteByteBuffer", "(Ljava/io/OutputStream;Ljava/nio/ByteBuffer;J)V",
- (void*) DngCreator_nativeWriteByteBuffer},
- {"nativeWriteInputStream", "(Ljava/io/OutputStream;Ljava/io/InputStream;J)V",
+ {"nativeWriteInputStream", "(Ljava/io/OutputStream;Ljava/io/InputStream;IIJ)V",
(void*) DngCreator_nativeWriteInputStream},
};
diff --git a/core/jni/android_hardware_camera2_legacy_PerfMeasurement.cpp b/core/jni/android_hardware_camera2_legacy_PerfMeasurement.cpp
new file mode 100644
index 0000000..93473a5
--- /dev/null
+++ b/core/jni/android_hardware_camera2_legacy_PerfMeasurement.cpp
@@ -0,0 +1,335 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "Camera2-Legacy-PerfMeasurement-JNI"
+#include <utils/Log.h>
+#include <utils/Errors.h>
+#include <utils/Trace.h>
+#include <utils/Vector.h>
+
+#include "jni.h"
+#include "JNIHelp.h"
+#include "android_runtime/AndroidRuntime.h"
+
+#include <ui/GraphicBuffer.h>
+#include <system/window.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+using namespace android;
+
+// fully-qualified class name
+#define PERF_MEASUREMENT_CLASS_NAME "android/hardware/camera2/legacy/PerfMeasurement"
+
+/** GL utility methods copied from com_google_android_gles_jni_GLImpl.cpp */
+
+// Check if the extension at the head of pExtensions is pExtension. Note that pExtensions is
+// terminated by either 0 or space, while pExtension is terminated by 0.
+
+static bool
+extensionEqual(const GLubyte* pExtensions, const GLubyte* pExtension) {
+ while (true) {
+ char a = *pExtensions++;
+ char b = *pExtension++;
+ bool aEnd = a == '\0' || a == ' ';
+ bool bEnd = b == '\0';
+ if (aEnd || bEnd) {
+ return aEnd == bEnd;
+ }
+ if (a != b) {
+ return false;
+ }
+ }
+}
+
+static const GLubyte*
+nextExtension(const GLubyte* pExtensions) {
+ while (true) {
+ char a = *pExtensions++;
+ if (a == '\0') {
+ return pExtensions-1;
+ } else if ( a == ' ') {
+ return pExtensions;
+ }
+ }
+}
+
+static bool
+checkForExtension(const GLubyte* pExtensions, const GLubyte* pExtension) {
+ for (; *pExtensions != '\0'; pExtensions = nextExtension(pExtensions)) {
+ if (extensionEqual(pExtensions, pExtension)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/** End copied GL utility methods */
+
+bool checkGlError(JNIEnv* env) {
+ int error;
+ if ((error = glGetError()) != GL_NO_ERROR) {
+ jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
+ "GLES20 error: 0x%d", error);
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Asynchronous low-overhead GL performance measurement using
+ * http://www.khronos.org/registry/gles/extensions/EXT/EXT_disjoint_timer_query.txt
+ *
+ * Measures the duration of GPU processing for a set of GL commands, delivering
+ * the measurement asynchronously once processing completes.
+ *
+ * All calls must come from a single thread with a valid GL context active.
+ **/
+class PerfMeasurementContext {
+ private:
+ Vector<GLuint> mTimingQueries;
+ size_t mTimingStartIndex;
+ size_t mTimingEndIndex;
+ size_t mTimingQueryIndex;
+ size_t mFreeQueries;
+
+ bool mInitDone;
+ public:
+
+ /**
+ * maxQueryCount should be a conservative estimate of how many query objects
+ * will be active at once, which is a function of the GPU's level of
+ * pipelining and the frequency of queries.
+ */
+ PerfMeasurementContext(size_t maxQueryCount):
+ mTimingStartIndex(0),
+ mTimingEndIndex(0),
+ mTimingQueryIndex(0) {
+ mTimingQueries.resize(maxQueryCount);
+ mFreeQueries = maxQueryCount;
+ mInitDone = false;
+ }
+
+ int getMaxQueryCount() {
+ return mTimingQueries.size();
+ }
+
+ /**
+ * Start a measurement period using the next available query object.
+ * Returns INVALID_OPERATION if called multiple times in a row,
+ * and BAD_VALUE if no more query objects are available.
+ */
+ int startGlTimer() {
+ // Lazy init of queries to avoid needing GL context during construction
+ if (!mInitDone) {
+ glGenQueriesEXT(mTimingQueries.size(), mTimingQueries.editArray());
+ mInitDone = true;
+ }
+
+ if (mTimingEndIndex != mTimingStartIndex) {
+ return INVALID_OPERATION;
+ }
+
+ if (mFreeQueries == 0) {
+ return BAD_VALUE;
+ }
+
+ glBeginQueryEXT(GL_TIME_ELAPSED_EXT, mTimingQueries[mTimingStartIndex]);
+
+ mTimingStartIndex = (mTimingStartIndex + 1) % mTimingQueries.size();
+ mFreeQueries--;
+
+ return OK;
+ }
+
+ /**
+ * Finish the current measurement period
+ * Returns INVALID_OPERATION if called before any startGLTimer calls
+ * or if called multiple times in a row.
+ */
+ int stopGlTimer() {
+ size_t nextEndIndex = (mTimingEndIndex + 1) % mTimingQueries.size();
+ if (nextEndIndex != mTimingStartIndex) {
+ return INVALID_OPERATION;
+ }
+ glEndQueryEXT(GL_TIME_ELAPSED_EXT);
+
+ mTimingEndIndex = nextEndIndex;
+
+ return OK;
+ }
+
+ static const nsecs_t NO_DURATION_YET = -1L;
+ static const nsecs_t FAILED_MEASUREMENT = -2L;
+
+ /**
+ * Get the next available duration measurement.
+ *
+ * Returns NO_DURATION_YET if no new measurement is available,
+ * and FAILED_MEASUREMENT if an error occurred during the next
+ * measurement period.
+ *
+ * Otherwise returns a positive number of nanoseconds measuring the
+ * duration of the oldest completed query.
+ */
+ nsecs_t getNextGlDuration() {
+ if (!mInitDone) {
+ // No start/stop called yet
+ return NO_DURATION_YET;
+ }
+
+ GLint available;
+ glGetQueryObjectivEXT(mTimingQueries[mTimingQueryIndex],
+ GL_QUERY_RESULT_AVAILABLE_EXT, &available);
+ if (!available) {
+ return NO_DURATION_YET;
+ }
+
+ GLint64 duration = FAILED_MEASUREMENT;
+ GLint disjointOccurred;
+ glGetIntegerv(GL_GPU_DISJOINT_EXT, &disjointOccurred);
+
+ if (!disjointOccurred) {
+ glGetQueryObjecti64vEXT(mTimingQueries[mTimingQueryIndex],
+ GL_QUERY_RESULT_EXT,
+ &duration);
+ }
+
+ mTimingQueryIndex = (mTimingQueryIndex + 1) % mTimingQueries.size();
+ mFreeQueries++;
+
+ return static_cast<nsecs_t>(duration);
+ }
+
+ static bool isMeasurementSupported() {
+ const GLubyte* extensions = glGetString(GL_EXTENSIONS);
+ return checkForExtension(extensions,
+ reinterpret_cast<const GLubyte*>("GL_EXT_disjoint_timer_query"));
+ }
+
+};
+
+PerfMeasurementContext* getContext(jlong context) {
+ return reinterpret_cast<PerfMeasurementContext*>(context);
+}
+
+extern "C" {
+
+static jlong PerfMeasurement_nativeCreateContext(JNIEnv* env, jobject thiz,
+ jint maxQueryCount) {
+ PerfMeasurementContext *context = new PerfMeasurementContext(maxQueryCount);
+ return reinterpret_cast<jlong>(context);
+}
+
+static void PerfMeasurement_nativeDeleteContext(JNIEnv* env, jobject thiz,
+ jlong contextHandle) {
+ PerfMeasurementContext *context = getContext(contextHandle);
+ delete(context);
+}
+
+static jboolean PerfMeasurement_nativeQuerySupport(JNIEnv* env, jobject thiz) {
+ bool supported = PerfMeasurementContext::isMeasurementSupported();
+ checkGlError(env);
+ return static_cast<jboolean>(supported);
+}
+
+static void PerfMeasurement_nativeStartGlTimer(JNIEnv* env, jobject thiz,
+ jlong contextHandle) {
+
+ PerfMeasurementContext *context = getContext(contextHandle);
+ status_t err = context->startGlTimer();
+ if (err != OK) {
+ switch (err) {
+ case INVALID_OPERATION:
+ jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
+ "Mismatched start/end GL timing calls");
+ return;
+ case BAD_VALUE:
+ jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
+ "Too many timing queries in progress, max %d",
+ context->getMaxQueryCount());
+ return;
+ default:
+ jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
+ "Unknown error starting GL timing");
+ return;
+ }
+ }
+ checkGlError(env);
+}
+
+static void PerfMeasurement_nativeStopGlTimer(JNIEnv* env, jobject thiz,
+ jlong contextHandle) {
+
+ PerfMeasurementContext *context = getContext(contextHandle);
+ status_t err = context->stopGlTimer();
+ if (err != OK) {
+ switch (err) {
+ case INVALID_OPERATION:
+ jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
+ "Mismatched start/end GL timing calls");
+ return;
+ default:
+ jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
+ "Unknown error ending GL timing");
+ return;
+ }
+ }
+ checkGlError(env);
+}
+
+static jlong PerfMeasurement_nativeGetNextGlDuration(JNIEnv* env,
+ jobject thiz, jlong contextHandle) {
+ PerfMeasurementContext *context = getContext(contextHandle);
+ nsecs_t duration = context->getNextGlDuration();
+
+ checkGlError(env);
+ return static_cast<jlong>(duration);
+}
+
+} // extern "C"
+
+static JNINativeMethod gPerfMeasurementMethods[] = {
+ { "nativeCreateContext",
+ "(I)J",
+ (jlong *)PerfMeasurement_nativeCreateContext },
+ { "nativeDeleteContext",
+ "(J)V",
+ (void *)PerfMeasurement_nativeDeleteContext },
+ { "nativeQuerySupport",
+ "()Z",
+ (jboolean *)PerfMeasurement_nativeQuerySupport },
+ { "nativeStartGlTimer",
+ "(J)V",
+ (void *)PerfMeasurement_nativeStartGlTimer },
+ { "nativeStopGlTimer",
+ "(J)V",
+ (void *)PerfMeasurement_nativeStopGlTimer },
+ { "nativeGetNextGlDuration",
+ "(J)J",
+ (jlong *)PerfMeasurement_nativeGetNextGlDuration }
+};
+
+
+// Get all the required offsets in java class and register native functions
+int register_android_hardware_camera2_legacy_PerfMeasurement(JNIEnv* env)
+{
+ // Register native functions
+ return AndroidRuntime::registerNativeMethods(env,
+ PERF_MEASUREMENT_CLASS_NAME,
+ gPerfMeasurementMethods,
+ NELEM(gPerfMeasurementMethods));
+}
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 53eb9d6..b4a4098 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -730,6 +730,9 @@
<attr name="actionBarTabTextStyle" format="reference" />
<attr name="actionOverflowButtonStyle" format="reference" />
<attr name="actionOverflowMenuStyle" format="reference" />
+ <!-- Reference to a theme that should be used to inflate popups
+ shown by widgets in the action bar. -->
+ <attr name="actionBarPopupTheme" format="reference" />
<!-- Reference to a style for the Action Bar -->
<attr name="actionBarStyle" format="reference" />
<!-- Reference to a style for the split Action Bar. This style
@@ -6780,10 +6783,20 @@
<attr name="itemPadding" format="dimension" />
<!-- Set true to hide the action bar on a vertical nested scroll of content. -->
<attr name="hideOnContentScroll" format="boolean" />
+ <!-- Minimum inset for content views within a bar. Navigation buttons and
+ menu views are excepted. Only valid for some themes and configurations. -->
<attr name="contentInsetStart" format="dimension" />
+ <!-- Minimum inset for content views within a bar. Navigation buttons and
+ menu views are excepted. Only valid for some themes and configurations. -->
<attr name="contentInsetEnd" format="dimension" />
+ <!-- Minimum inset for content views within a bar. Navigation buttons and
+ menu views are excepted. Only valid for some themes and configurations. -->
<attr name="contentInsetLeft" format="dimension" />
+ <!-- Minimum inset for content views within a bar. Navigation buttons and
+ menu views are excepted. Only valid for some themes and configurations. -->
<attr name="contentInsetRight" format="dimension" />
+ <!-- Elevation for the action bar itself -->
+ <attr name="elevation" />
</declare-styleable>
<declare-styleable name="ActionMode">
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 2dd67ba..ca0dc1a 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2232,6 +2232,7 @@
<public type="attr" name="buttonBarNeutralButtonStyle" />
<public type="attr" name="buttonBarNegativeButtonStyle" />
<public type="attr" name="popupElevation" />
+ <public type="attr" name="actionBarPopupTheme" />
<public-padding type="dimen" name="l_resource_pad" end="0x01050010" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index e8bd99a..7c60c6e 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2152,8 +2152,8 @@
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_accessDrmCertificates">Allows an application to provision and use DRM certficates. Should never be needed for normal apps.</string>
- <string name="permlab_handoverStatus">Receive handover transfer broadcasts.</string>
- <string name="permdesc_handoverStatus">Allows receiving handover transfer status information.</string>
+ <string name="permlab_handoverStatus">Receive Android Beam transfer status</string>
+ <string name="permdesc_handoverStatus">Allows this application to receive information about current Android Beam transfers</string>
<!-- Policy administration -->
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index 3880fb3..4623258 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -804,6 +804,7 @@
<item name="homeLayout">@layout/action_bar_home_material</item>
<item name="gravity">center_vertical</item>
<item name="contentInsetStart">16dp</item>
+ <item name="elevation">8dp</item>
</style>
<style name="Widget.Material.ActionBar.Solid">
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index b63a8c1..d61253f 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -362,6 +362,7 @@
<item name="actionMenuTextAppearance">@style/TextAppearance.Holo.Widget.ActionBar.Menu</item>
<item name="actionMenuTextColor">?attr/textColorPrimary</item>
<item name="actionBarWidgetTheme">@null</item>
+ <item name="actionBarPopupTheme">@null</item>
<item name="actionBarTheme">@null</item>
<item name="actionBarDivider">?attr/dividerVertical</item>
<item name="actionBarItemBackground">?attr/selectableItemBackground</item>
diff --git a/core/res/res/values/themes_holo.xml b/core/res/res/values/themes_holo.xml
index 78405e3..a9150c6 100644
--- a/core/res/res/values/themes_holo.xml
+++ b/core/res/res/values/themes_holo.xml
@@ -341,6 +341,7 @@
<item name="actionBarSize">@dimen/action_bar_default_height</item>
<item name="actionModePopupWindowStyle">@style/Widget.Holo.PopupWindow.ActionMode</item>
<item name="actionBarWidgetTheme">@null</item>
+ <item name="actionBarPopupTheme">@null</item>
<item name="actionBarTheme">@null</item>
<item name="actionModeCutDrawable">@drawable/ic_menu_cut_holo_dark</item>
diff --git a/core/res/res/values/themes_material.xml b/core/res/res/values/themes_material.xml
index 472177f..47f3778 100644
--- a/core/res/res/values/themes_material.xml
+++ b/core/res/res/values/themes_material.xml
@@ -315,6 +315,7 @@
<item name="actionBarSize">@dimen/action_bar_default_height_material</item>
<item name="actionModePopupWindowStyle">@style/Widget.Material.PopupWindow.ActionMode</item>
<item name="actionBarWidgetTheme">@null</item>
+ <item name="actionBarPopupTheme">@null</item>
<item name="actionBarTheme">@style/ThemeOverlay.Material.ActionBar</item>
<item name="actionBarItemBackground">?attr/selectableItemBackgroundBorderless</item>
@@ -498,7 +499,7 @@
<item name="windowFullscreen">false</item>
<item name="windowOverscan">false</item>
<item name="windowIsFloating">false</item>
- <item name="windowContentOverlay">@drawable/ab_solid_shadow_material</item>
+ <item name="windowContentOverlay">@null</item>
<item name="windowShowWallpaper">false</item>
<item name="windowTitleStyle">@style/WindowTitle.Material</item>
<item name="windowTitleSize">@dimen/action_bar_default_height_material</item>
@@ -653,6 +654,7 @@
<item name="actionButtonStyle">@style/Widget.Material.Light.ActionButton</item>
<item name="actionOverflowButtonStyle">@style/Widget.Material.Light.ActionButton.Overflow</item>
<item name="actionOverflowMenuStyle">@style/Widget.Material.Light.PopupMenu.Overflow</item>
+ <item name="actionBarPopupTheme">@null</item>
<item name="actionModeBackground">@drawable/cab_background_top_holo_light</item>
<item name="actionModeSplitBackground">@drawable/cab_background_bottom_holo_light</item>
<item name="actionModeCloseDrawable">@drawable/ic_cab_done_material</item>
@@ -737,6 +739,7 @@
<style name="Theme.Material.Light.DarkActionBar">
<item name="actionBarWidgetTheme">@null</item>
<item name="actionBarTheme">@style/ThemeOverlay.Material.Dark.ActionBar</item>
+ <item name="actionBarPopupTheme">@style/ThemeOverlay.Material.Light</item>
<item name="colorPrimaryDark">@color/material_blue_grey_900</item>
<item name="colorPrimary">@color/material_blue_grey_800</item>
@@ -778,6 +781,7 @@
<item name="fastScrollPreviewBackgroundLeft">@drawable/fastscroll_label_left_holo_light</item>
<item name="fastScrollPreviewBackgroundRight">@drawable/fastscroll_label_right_holo_light</item>
+ <item name="colorControlNormal">?attr/textColorSecondary</item>
<item name="colorControlHighlight">@color/ripple_material_light</item>
<item name="colorButtonNormal">@color/btn_default_material_light</item>
</style>
@@ -814,6 +818,7 @@
<item name="fastScrollPreviewBackgroundLeft">@drawable/fastscroll_label_left_holo_dark</item>
<item name="fastScrollPreviewBackgroundRight">@drawable/fastscroll_label_right_holo_dark</item>
+ <item name="colorControlNormal">?attr/textColorSecondary</item>
<item name="colorControlHighlight">@color/ripple_material_dark</item>
<item name="colorButtonNormal">@color/btn_default_material_dark</item>
</style>
diff --git a/data/fonts/fallback_fonts.xml b/data/fonts/fallback_fonts.xml
index a7e159a..0f0281b 100644
--- a/data/fonts/fallback_fonts.xml
+++ b/data/fonts/fallback_fonts.xml
@@ -234,12 +234,12 @@
</family>
<family>
<fileset>
- <file lang="zh-CN">NotoSansHans-Regular.otf</file>
+ <file lang="zh-Hans">NotoSansHans-Regular.otf</file>
</fileset>
</family>
<family>
<fileset>
- <file lang="zh-TW">NotoSansHant-Regular.otf</file>
+ <file lang="zh-Hant">NotoSansHant-Regular.otf</file>
</fileset>
</family>
<family>
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index b050a02..e7a073c 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -1019,9 +1019,6 @@
drawable = new StateListDrawable();
} else if (name.equals("animated-selector")) {
drawable = new AnimatedStateListDrawable();
- } else if (name.equals("material-progress")) {
- // TODO: Replace this with something less ridiculous.
- drawable = new MaterialProgressDrawable();
} else if (name.equals("level-list")) {
drawable = new LevelListDrawable();
} else if (name.equals("layer-list")) {
diff --git a/graphics/java/android/graphics/drawable/MaterialProgressDrawable.java b/graphics/java/android/graphics/drawable/MaterialProgressDrawable.java
deleted file mode 100644
index c484094..0000000
--- a/graphics/java/android/graphics/drawable/MaterialProgressDrawable.java
+++ /dev/null
@@ -1,548 +0,0 @@
-/*
- * 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.graphics.drawable;
-
-import android.animation.Animator;
-import android.animation.ObjectAnimator;
-import android.animation.TimeInterpolator;
-import android.content.res.ColorStateList;
-import android.content.res.Resources;
-import android.content.res.Resources.Theme;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.ColorFilter;
-import android.graphics.Paint;
-import android.graphics.PorterDuffColorFilter;
-import android.graphics.Paint.Cap;
-import android.graphics.Paint.Style;
-import android.graphics.PorterDuff.Mode;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.util.AttributeSet;
-import android.view.animation.AccelerateDecelerateInterpolator;
-import android.view.animation.LinearInterpolator;
-
-import com.android.internal.R;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-import java.util.ArrayList;
-
-/**
- * Fancy progress indicator for Material theme.
- *
- * TODO: Replace this class with something less ridiculous.
- */
-class MaterialProgressDrawable extends Drawable implements Animatable {
- private static final TimeInterpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
- private static final TimeInterpolator END_CURVE_INTERPOLATOR = new EndCurveInterpolator();
- private static final TimeInterpolator START_CURVE_INTERPOLATOR = new StartCurveInterpolator();
-
- /** The duration of a single progress spin in milliseconds. */
- private static final int ANIMATION_DURATION = 1000 * 80 / 60;
-
- /** The number of points in the progress "star". */
- private static final int NUM_POINTS = 5;
-
- /** The list of animators operating on this drawable. */
- private final ArrayList<Animator> mAnimators = new ArrayList<Animator>();
-
- /** The indicator ring, used to manage animation state. */
- private final Ring mRing;
-
- private MaterialProgressState mState;
- private PorterDuffColorFilter mTintFilter;
-
- /** Canvas rotation in degrees. */
- private float mRotation;
-
- private boolean mMutated;
-
- public MaterialProgressDrawable() {
- this(new MaterialProgressState(null), null);
- }
-
- private MaterialProgressDrawable(MaterialProgressState state, Theme theme) {
- mState = state;
- if (theme != null && state.canApplyTheme()) {
- applyTheme(theme);
- }
-
- mRing = new Ring(mCallback);
- mMutated = false;
-
- initializeFromState();
- setupAnimators();
- }
-
- private void initializeFromState() {
- final MaterialProgressState state = mState;
-
- final Ring ring = mRing;
- ring.setStrokeWidth(state.mStrokeWidth);
-
- final int color = state.mColor.getColorForState(getState(), Color.TRANSPARENT);
- ring.setColor(color);
-
- final float minEdge = Math.min(state.mWidth, state.mHeight);
- if (state.mInnerRadius <= 0 || minEdge < 0) {
- ring.setInsets((int) Math.ceil(state.mStrokeWidth / 2.0f));
- } else {
- float insets = minEdge / 2.0f - state.mInnerRadius;
- ring.setInsets(insets);
- }
-
- mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
- }
-
- @Override
- public Drawable mutate() {
- if (!mMutated && super.mutate() == this) {
- mState = new MaterialProgressState(mState);
- mMutated = true;
- }
- return this;
- }
-
- @Override
- protected boolean onStateChange(int[] stateSet) {
- boolean changed = super.onStateChange(stateSet);
-
- final MaterialProgressState state = mState;
- final int color = state.mColor.getColorForState(stateSet, Color.TRANSPARENT);
- if (color != mRing.getColor()) {
- mRing.setColor(color);
- changed = true;
- }
-
- if (state.mTint != null && state.mTintMode != null) {
- mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
- changed = true;
- }
-
- return changed;
- }
-
- @Override
- public boolean isStateful() {
- return super.isStateful() || mState.mColor.isStateful();
- }
-
- @Override
- public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
- throws XmlPullParserException, IOException {
- final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.MaterialProgressDrawable);
- super.inflateWithAttributes(r, parser, a, R.styleable.MaterialProgressDrawable_visible);
- updateStateFromTypedArray(a);
- a.recycle();
-
- initializeFromState();
- }
-
- @Override
- public void applyTheme(Theme t) {
- final TypedArray a = t.resolveAttributes(mState.mThemeAttrs,
- R.styleable.MaterialProgressDrawable);
- updateStateFromTypedArray(a);
- a.recycle();
- }
-
- private void updateStateFromTypedArray(TypedArray a) {
- final MaterialProgressState state = mState;
- state.mThemeAttrs = a.extractThemeAttrs();
- state.mWidth = a.getDimensionPixelSize(
- R.styleable.MaterialProgressDrawable_width, state.mWidth);
- state.mHeight = a.getDimensionPixelSize(
- R.styleable.MaterialProgressDrawable_height, state.mHeight);
- state.mInnerRadius = a.getDimension(
- R.styleable.MaterialProgressDrawable_innerRadius, state.mInnerRadius);
- state.mStrokeWidth = a.getDimension(
- R.styleable.MaterialProgressDrawable_thickness, state.mStrokeWidth);
-
- if (a.hasValue(R.styleable.MaterialProgressDrawable_color)) {
- state.mColor = a.getColorStateList(R.styleable.MaterialProgressDrawable_color);
- }
- }
-
- @Override
- public boolean setVisible(boolean visible, boolean restart) {
- boolean changed = super.setVisible(visible, restart);
- if (visible) {
- if (changed || restart) {
- start();
- }
- } else {
- stop();
- }
- return changed;
- }
-
- @Override
- public int getIntrinsicHeight() {
- return mState.mHeight;
- }
-
- @Override
- public int getIntrinsicWidth() {
- return mState.mWidth;
- }
-
- @Override
- public void draw(Canvas c) {
- final Rect bounds = getBounds();
- final int saveCount = c.save();
- c.rotate(mRotation, bounds.exactCenterX(), bounds.exactCenterY());
- mRing.draw(c, bounds);
- c.restoreToCount(saveCount);
- }
-
- @Override
- public void setAlpha(int alpha) {
- mRing.setAlpha(alpha);
- }
-
- @Override
- public int getAlpha() {
- return mRing.getAlpha();
- }
-
- @Override
- public void setColorFilter(ColorFilter colorFilter) {
- mRing.setColorFilter(colorFilter);
- }
-
- @Override
- public ColorFilter getColorFilter() {
- return mRing.getColorFilter();
- }
-
- @Override
- public void setTint(ColorStateList tint, Mode tintMode) {
- if (mState.mTint != tint || mState.mTintMode != tintMode) {
- mState.mTint = tint;
- mState.mTintMode = tintMode;
-
- mTintFilter = updateTintFilter(mTintFilter, tint, tintMode);
- invalidateSelf();
- }
- }
-
- @SuppressWarnings("unused")
- private void setRotation(float rotation) {
- mRotation = rotation;
- invalidateSelf();
- }
-
- @SuppressWarnings("unused")
- private float getRotation() {
- return mRotation;
- }
-
- @Override
- public int getOpacity() {
- return PixelFormat.TRANSLUCENT;
- }
-
- @Override
- public boolean isRunning() {
- final ArrayList<Animator> animators = mAnimators;
- final int N = animators.size();
- for (int i = 0; i < N; i++) {
- final Animator animator = animators.get(i);
- if (animator.isRunning()) {
- return true;
- }
- }
- return false;
- }
-
- @Override
- public void start() {
- final ArrayList<Animator> animators = mAnimators;
- final int N = animators.size();
- for (int i = 0; i < N; i++) {
- final Animator animator = animators.get(i);
- if (animator.isPaused()) {
- animator.resume();
- } else if (!animator.isRunning()){
- animator.start();
- }
- }
- }
-
- @Override
- public void stop() {
- final ArrayList<Animator> animators = mAnimators;
- final int N = animators.size();
- for (int i = 0; i < N; i++) {
- final Animator animator = animators.get(i);
- animator.pause();
- }
- }
-
- private void setupAnimators() {
- final Ring ring = mRing;
-
- final ObjectAnimator endTrim = ObjectAnimator.ofFloat(ring, "endTrim", 0, 0.75f);
- endTrim.setDuration(ANIMATION_DURATION);
- endTrim.setInterpolator(START_CURVE_INTERPOLATOR);
- endTrim.setRepeatCount(ObjectAnimator.INFINITE);
- endTrim.setRepeatMode(ObjectAnimator.RESTART);
-
- final ObjectAnimator startTrim = ObjectAnimator.ofFloat(ring, "startTrim", 0.0f, 0.75f);
- startTrim.setDuration(ANIMATION_DURATION);
- startTrim.setInterpolator(END_CURVE_INTERPOLATOR);
- startTrim.setRepeatCount(ObjectAnimator.INFINITE);
- startTrim.setRepeatMode(ObjectAnimator.RESTART);
-
- final ObjectAnimator rotation = ObjectAnimator.ofFloat(ring, "rotation", 0.0f, 0.25f);
- rotation.setDuration(ANIMATION_DURATION);
- rotation.setInterpolator(LINEAR_INTERPOLATOR);
- rotation.setRepeatCount(ObjectAnimator.INFINITE);
- rotation.setRepeatMode(ObjectAnimator.RESTART);
-
- final ObjectAnimator groupRotation = ObjectAnimator.ofFloat(this, "rotation", 0.0f, 360.0f);
- groupRotation.setDuration(NUM_POINTS * ANIMATION_DURATION);
- groupRotation.setInterpolator(LINEAR_INTERPOLATOR);
- groupRotation.setRepeatCount(ObjectAnimator.INFINITE);
- groupRotation.setRepeatMode(ObjectAnimator.RESTART);
-
- mAnimators.add(endTrim);
- mAnimators.add(startTrim);
- mAnimators.add(rotation);
- mAnimators.add(groupRotation);
- }
-
- private final Callback mCallback = new Callback() {
- @Override
- public void invalidateDrawable(Drawable d) {
- invalidateSelf();
- }
-
- @Override
- public void scheduleDrawable(Drawable d, Runnable what, long when) {
- scheduleSelf(what, when);
- }
-
- @Override
- public void unscheduleDrawable(Drawable d, Runnable what) {
- unscheduleSelf(what);
- }
- };
-
- private static class MaterialProgressState extends ConstantState {
- private int[] mThemeAttrs = null;
- private float mStrokeWidth = 5.0f;
- private float mInnerRadius = -1.0f;
- private int mWidth = -1;
- private int mHeight = -1;
- private ColorStateList mColor = ColorStateList.valueOf(Color.TRANSPARENT);
- private ColorStateList mTint = null;
- private Mode mTintMode = null;
-
- public MaterialProgressState(MaterialProgressState orig) {
- if (orig != null) {
- mThemeAttrs = orig.mThemeAttrs;
- mStrokeWidth = orig.mStrokeWidth;
- mInnerRadius = orig.mInnerRadius;
- mWidth = orig.mWidth;
- mHeight = orig.mHeight;
- mColor = orig.mColor;
- mTint = orig.mTint;
- mTintMode = orig.mTintMode;
- }
- }
-
- @Override
- public boolean canApplyTheme() {
- return mThemeAttrs != null;
- }
-
- @Override
- public Drawable newDrawable() {
- return newDrawable(null, null);
- }
-
- @Override
- public Drawable newDrawable(Resources res) {
- return newDrawable(res, null);
- }
-
- @Override
- public Drawable newDrawable(Resources res, Theme theme) {
- return new MaterialProgressDrawable(this, theme);
- }
-
- @Override
- public int getChangingConfigurations() {
- return 0;
- }
- }
-
- private static class Ring {
- private final RectF mTempBounds = new RectF();
- private final Paint mPaint = new Paint();
-
- private final Callback mCallback;
-
- private float mStartTrim = 0.0f;
- private float mEndTrim = 0.0f;
- private float mRotation = 0.0f;
- private float mStrokeWidth = 5.0f;
- private float mStrokeInset = 2.5f;
-
- private int mAlpha = 0xFF;
- private int mColor = Color.BLACK;
-
- public Ring(Callback callback) {
- mCallback = callback;
-
- mPaint.setStrokeCap(Cap.ROUND);
- mPaint.setAntiAlias(true);
- mPaint.setStyle(Style.STROKE);
- }
-
- public void draw(Canvas c, Rect bounds) {
- final RectF arcBounds = mTempBounds;
- arcBounds.set(bounds);
- arcBounds.inset(mStrokeInset, mStrokeInset);
-
- final float startAngle = (mStartTrim + mRotation) * 360;
- final float endAngle = (mEndTrim + mRotation) * 360;
- float sweepAngle = endAngle - startAngle;
-
- // Ensure the sweep angle isn't too small to draw.
- final float diameter = Math.min(arcBounds.width(), arcBounds.height());
- final float minAngle = (float) (360.0 / (diameter * Math.PI));
- if (sweepAngle < minAngle && sweepAngle > -minAngle) {
- sweepAngle = Math.signum(sweepAngle) * minAngle;
- }
-
- c.drawArc(arcBounds, startAngle, sweepAngle, false, mPaint);
- }
-
- public void setColorFilter(ColorFilter filter) {
- mPaint.setColorFilter(filter);
- invalidateSelf();
- }
-
- public ColorFilter getColorFilter() {
- return mPaint.getColorFilter();
- }
-
- public void setAlpha(int alpha) {
- mAlpha = alpha;
- mPaint.setColor(mColor & 0xFFFFFF | alpha << 24);
- invalidateSelf();
- }
-
- public int getAlpha() {
- return mAlpha;
- }
-
- public void setColor(int color) {
- mColor = color;
- mPaint.setColor(color & 0xFFFFFF | mAlpha << 24);
- invalidateSelf();
- }
-
- public int getColor() {
- return mColor;
- }
-
- public void setStrokeWidth(float strokeWidth) {
- mStrokeWidth = strokeWidth;
- mPaint.setStrokeWidth(strokeWidth);
- invalidateSelf();
- }
-
- @SuppressWarnings("unused")
- public float getStrokeWidth() {
- return mStrokeWidth;
- }
-
- @SuppressWarnings("unused")
- public void setStartTrim(float startTrim) {
- mStartTrim = startTrim;
- invalidateSelf();
- }
-
- @SuppressWarnings("unused")
- public float getStartTrim() {
- return mStartTrim;
- }
-
- @SuppressWarnings("unused")
- public void setEndTrim(float endTrim) {
- mEndTrim = endTrim;
- invalidateSelf();
- }
-
- @SuppressWarnings("unused")
- public float getEndTrim() {
- return mEndTrim;
- }
-
- @SuppressWarnings("unused")
- public void setRotation(float rotation) {
- mRotation = rotation;
- invalidateSelf();
- }
-
- @SuppressWarnings("unused")
- public float getRotation() {
- return mRotation;
- }
-
- public void setInsets(float insets) {
- mStrokeInset = insets;
- }
-
- @SuppressWarnings("unused")
- public float getInsets() {
- return mStrokeInset;
- }
-
- private void invalidateSelf() {
- mCallback.invalidateDrawable(null);
- }
- }
-
- /**
- * Squishes the interpolation curve into the second half of the animation.
- */
- private static class EndCurveInterpolator extends AccelerateDecelerateInterpolator {
- @Override
- public float getInterpolation(float input) {
- return super.getInterpolation(Math.max(0, (input - 0.5f) * 2.0f));
- }
- }
-
- /**
- * Squishes the interpolation curve into the first half of the animation.
- */
- private static class StartCurveInterpolator extends AccelerateDecelerateInterpolator {
- @Override
- public float getInterpolation(float input) {
- return super.getInterpolation(Math.min(1, input * 2.0f));
- }
- }
-}
diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java
index 4ea0046..8783994 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -299,11 +299,10 @@
return color;
}
-
@Override
public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)
throws XmlPullParserException, IOException {
- final TypedArray a = obtainAttributes(res, theme, attrs,R.styleable.VectorDrawable);
+ final TypedArray a = obtainAttributes(res, theme, attrs, R.styleable.VectorDrawable);
updateStateFromTypedArray(a);
a.recycle();
@@ -358,7 +357,7 @@
if (SHAPE_PATH.equals(tagName)) {
final VPath path = new VPath();
path.inflate(res, attrs, theme);
- currentGroup.add(path);
+ currentGroup.mChildren.add(path);
if (path.getPathName() != null) {
pathRenderer.mVGTargetsMap.put(path.getPathName(), path);
}
@@ -375,7 +374,7 @@
} else if (SHAPE_GROUP.equals(tagName)) {
VGroup newChildGroup = new VGroup();
newChildGroup.inflate(res, attrs, theme);
- currentGroup.mChildGroupList.add(newChildGroup);
+ currentGroup.mChildren.add(newChildGroup);
groupStack.push(newChildGroup);
if (newChildGroup.getGroupName() != null) {
pathRenderer.mVGTargetsMap.put(newChildGroup.getGroupName(),
@@ -424,16 +423,19 @@
private void printGroupTree(VGroup currentGroup, int level) {
String indent = "";
- for (int i = 0 ; i < level ; i++) {
+ for (int i = 0; i < level; i++) {
indent += " ";
}
// Print the current node
- Log.v(LOGTAG, indent + "current group is :" + currentGroup.getGroupName()
+ Log.v(LOGTAG, indent + "current group is :" + currentGroup.getGroupName()
+ " rotation is " + currentGroup.mRotate);
- Log.v(LOGTAG, indent + "matrix is :" + currentGroup.getLocalMatrix().toString());
- // Then print all the children
- for (int i = 0 ; i < currentGroup.mChildGroupList.size(); i++) {
- printGroupTree(currentGroup.mChildGroupList.get(i), level + 1);
+ Log.v(LOGTAG, indent + "matrix is :" + currentGroup.getLocalMatrix().toString());
+ // Then print all the children groups
+ for (int i = 0; i < currentGroup.mChildren.size(); i++) {
+ Object child = currentGroup.mChildren.get(i);
+ if (child instanceof VGroup) {
+ printGroupTree((VGroup) child, level + 1);
+ }
}
}
@@ -552,21 +554,21 @@
private boolean recursiveCanApplyTheme(VGroup currentGroup) {
// We can do a tree traverse here, if there is one path return true,
// then we return true for the whole tree.
- final ArrayList<VPath> paths = currentGroup.mPathList;
- for (int j = paths.size() - 1; j >= 0; j--) {
- final VPath path = paths.get(j);
- if (path.canApplyTheme()) {
- return true;
- }
- }
+ final ArrayList<Object> children = currentGroup.mChildren;
- final ArrayList<VGroup> childGroups = currentGroup.mChildGroupList;
-
- for (int i = 0; i < childGroups.size(); i++) {
- VGroup childGroup = childGroups.get(i);
- if (childGroup.canApplyTheme()
- || recursiveCanApplyTheme(childGroup)) {
- return true;
+ for (int i = 0; i < children.size(); i++) {
+ Object child = children.get(i);
+ if (child instanceof VGroup) {
+ VGroup childGroup = (VGroup) child;
+ if (childGroup.canApplyTheme()
+ || recursiveCanApplyTheme(childGroup)) {
+ return true;
+ }
+ } else if (child instanceof VPath) {
+ VPath childPath = (VPath) child;
+ if (childPath.canApplyTheme()) {
+ return true;
+ }
}
}
return false;
@@ -580,24 +582,22 @@
private void recursiveApplyTheme(VGroup currentGroup, Theme t) {
// We can do a tree traverse here, apply theme to all paths which
// can apply theme.
- final ArrayList<VPath> paths = currentGroup.mPathList;
- for (int j = paths.size() - 1; j >= 0; j--) {
- final VPath path = paths.get(j);
- if (path.canApplyTheme()) {
- path.applyTheme(t);
+ final ArrayList<Object> children = currentGroup.mChildren;
+ for (int i = 0; i < children.size(); i++) {
+ Object child = children.get(i);
+ if (child instanceof VGroup) {
+ VGroup childGroup = (VGroup) child;
+ if (childGroup.canApplyTheme()) {
+ childGroup.applyTheme(t);
+ }
+ recursiveApplyTheme(childGroup, t);
+ } else if (child instanceof VPath) {
+ VPath childPath = (VPath) child;
+ if (childPath.canApplyTheme()) {
+ childPath.applyTheme(t);
+ }
}
}
-
- final ArrayList<VGroup> childGroups = currentGroup.mChildGroupList;
-
- for (int i = 0; i < childGroups.size(); i++) {
- VGroup childGroup = childGroups.get(i);
- if (childGroup.canApplyTheme()) {
- childGroup.applyTheme(t);
- }
- recursiveApplyTheme(childGroup, t);
- }
-
}
public void setColorFilter(ColorFilter colorFilter) {
@@ -624,11 +624,18 @@
currentGroup.mStackedMatrix.preConcat(currentGroup.mLocalMatrix);
float stackedAlpha = currentAlpha * currentGroup.mGroupAlpha;
- drawPath(currentGroup, stackedAlpha, canvas, w, h);
- // Draw the group tree in post order.
- for (int i = 0 ; i < currentGroup.mChildGroupList.size(); i++) {
- drawGroupTree(currentGroup.mChildGroupList.get(i),
- currentGroup.mStackedMatrix, stackedAlpha, canvas, w, h);
+
+ // Draw the group tree in the same order as the XML file.
+ for (int i = 0; i < currentGroup.mChildren.size(); i++) {
+ Object child = currentGroup.mChildren.get(i);
+ if (child instanceof VGroup) {
+ VGroup childGroup = (VGroup) child;
+ drawGroupTree(childGroup, currentGroup.mStackedMatrix,
+ stackedAlpha, canvas, w, h);
+ } else if (child instanceof VPath) {
+ VPath childPath = (VPath) child;
+ drawPath(currentGroup, childPath, stackedAlpha, canvas, w, h);
+ }
}
}
@@ -637,7 +644,8 @@
drawGroupTree(mRootGroup, IDENTITY_MATRIX, ((float) mRootAlpha) / 0xFF, canvas, w, h);
}
- private void drawPath(VGroup vGroup, float stackedAlpha, Canvas canvas, int w, int h) {
+ private void drawPath(VGroup vGroup, VPath vPath, float stackedAlpha,
+ Canvas canvas, int w, int h) {
final float scale = Math.min(h / mViewportHeight, w / mViewportWidth);
mFinalPathMatrix.set(vGroup.mStackedMatrix);
@@ -645,75 +653,71 @@
mFinalPathMatrix.postTranslate(
w / 2f - mViewportWidth / 2f, h / 2f - mViewportHeight / 2f);
- ArrayList<VPath> paths = vGroup.getPaths();
- for (int i = 0; i < paths.size(); i++) {
- VPath vPath = paths.get(i);
- vPath.toPath(mPath);
- final Path path = mPath;
+ vPath.toPath(mPath);
+ final Path path = mPath;
- if (vPath.mTrimPathStart != 0.0f || vPath.mTrimPathEnd != 1.0f) {
- float start = (vPath.mTrimPathStart + vPath.mTrimPathOffset) % 1.0f;
- float end = (vPath.mTrimPathEnd + vPath.mTrimPathOffset) % 1.0f;
+ if (vPath.mTrimPathStart != 0.0f || vPath.mTrimPathEnd != 1.0f) {
+ float start = (vPath.mTrimPathStart + vPath.mTrimPathOffset) % 1.0f;
+ float end = (vPath.mTrimPathEnd + vPath.mTrimPathOffset) % 1.0f;
- if (mPathMeasure == null) {
- mPathMeasure = new PathMeasure();
+ if (mPathMeasure == null) {
+ mPathMeasure = new PathMeasure();
+ }
+ mPathMeasure.setPath(mPath, false);
+
+ float len = mPathMeasure.getLength();
+ start = start * len;
+ end = end * len;
+ path.reset();
+ if (start > end) {
+ mPathMeasure.getSegment(start, len, path, true);
+ mPathMeasure.getSegment(0f, end, path, true);
+ } else {
+ mPathMeasure.getSegment(start, end, path, true);
+ }
+ path.rLineTo(0, 0); // fix bug in measure
+ }
+
+ mRenderPath.reset();
+
+ mRenderPath.addPath(path, mFinalPathMatrix);
+
+ if (vPath.mClip) {
+ canvas.clipPath(mRenderPath, Region.Op.REPLACE);
+ } else {
+ if (vPath.mFillColor != 0) {
+ if (mFillPaint == null) {
+ mFillPaint = new Paint();
+ mFillPaint.setColorFilter(mColorFilter);
+ mFillPaint.setStyle(Paint.Style.FILL);
+ mFillPaint.setAntiAlias(true);
}
- mPathMeasure.setPath(mPath, false);
-
- float len = mPathMeasure.getLength();
- start = start * len;
- end = end * len;
- path.reset();
- if (start > end) {
- mPathMeasure.getSegment(start, len, path, true);
- mPathMeasure.getSegment(0f, end, path, true);
- } else {
- mPathMeasure.getSegment(start, end, path, true);
- }
- path.rLineTo(0, 0); // fix bug in measure
+ mFillPaint.setColor(applyAlpha(vPath.mFillColor, stackedAlpha));
+ canvas.drawPath(mRenderPath, mFillPaint);
}
- mRenderPath.reset();
-
- mRenderPath.addPath(path, mFinalPathMatrix);
-
- if (vPath.mClip) {
- canvas.clipPath(mRenderPath, Region.Op.REPLACE);
- } else {
- if (vPath.mFillColor != 0) {
- if (mFillPaint == null) {
- mFillPaint = new Paint();
- mFillPaint.setColorFilter(mColorFilter);
- mFillPaint.setStyle(Paint.Style.FILL);
- mFillPaint.setAntiAlias(true);
- }
- mFillPaint.setColor(applyAlpha(vPath.mFillColor, stackedAlpha));
- canvas.drawPath(mRenderPath, mFillPaint);
+ if (vPath.mStrokeColor != 0) {
+ if (mStrokePaint == null) {
+ mStrokePaint = new Paint();
+ mStrokePaint.setColorFilter(mColorFilter);
+ mStrokePaint.setStyle(Paint.Style.STROKE);
+ mStrokePaint.setAntiAlias(true);
}
- if (vPath.mStrokeColor != 0) {
- if (mStrokePaint == null) {
- mStrokePaint = new Paint();
- mStrokePaint.setColorFilter(mColorFilter);
- mStrokePaint.setStyle(Paint.Style.STROKE);
- mStrokePaint.setAntiAlias(true);
- }
-
- final Paint strokePaint = mStrokePaint;
- if (vPath.mStrokeLineJoin != null) {
- strokePaint.setStrokeJoin(vPath.mStrokeLineJoin);
- }
-
- if (vPath.mStrokeLineCap != null) {
- strokePaint.setStrokeCap(vPath.mStrokeLineCap);
- }
-
- strokePaint.setStrokeMiter(vPath.mStrokeMiterlimit * scale);
-
- strokePaint.setColor(applyAlpha(vPath.mStrokeColor, stackedAlpha));
- strokePaint.setStrokeWidth(vPath.mStrokeWidth * scale);
- canvas.drawPath(mRenderPath, strokePaint);
+ final Paint strokePaint = mStrokePaint;
+ if (vPath.mStrokeLineJoin != null) {
+ strokePaint.setStrokeJoin(vPath.mStrokeLineJoin);
}
+
+ if (vPath.mStrokeLineCap != null) {
+ strokePaint.setStrokeCap(vPath.mStrokeLineCap);
+ }
+
+ strokePaint.setStrokeMiter(vPath.mStrokeMiterlimit * scale);
+
+ strokePaint.setColor(applyAlpha(vPath.mStrokeColor, stackedAlpha));
+ strokePaint.setStrokeWidth(vPath.mStrokeWidth * scale);
+ canvas.drawPath(mRenderPath, strokePaint);
}
}
}
@@ -742,7 +746,7 @@
}
private void parseSize(Resources r, AttributeSet attrs)
- throws XmlPullParserException {
+ throws XmlPullParserException {
final TypedArray a = r.obtainAttributes(attrs, R.styleable.VectorDrawableSize);
// Account for any configuration changes.
@@ -771,8 +775,7 @@
/////////////////////////////////////////////////////
// Variables below need to be copied (deep copy if applicable) for mutation.
- private final ArrayList<VPath> mPathList = new ArrayList<VPath>();
- private final ArrayList<VGroup> mChildGroupList = new ArrayList<VGroup>();
+ final ArrayList<Object> mChildren = new ArrayList<Object>();
private float mRotate = 0;
private float mPivotX = 0;
@@ -808,19 +811,21 @@
mLocalMatrix.set(copy.mLocalMatrix);
- for (int i = 0; i < copy.mPathList.size(); i ++) {
- VPath copyPath = copy.mPathList.get(i);
- VPath newPath = new VPath(copyPath);
- mPathList.add(newPath);
- if (newPath.mPathName != null) {
- targetsMap.put(copyPath.mPathName, newPath);
+ final ArrayList<Object> children = copy.mChildren;
+ for (int i = 0; i < children.size(); i++) {
+ Object copyChild = children.get(i);
+ if (copyChild instanceof VGroup) {
+ VGroup copyGroup = (VGroup) copyChild;
+ mChildren.add(new VGroup(copyGroup, targetsMap));
+ } else if (copyChild instanceof VPath) {
+ VPath copyPath = (VPath) copyChild;
+ VPath newPath = new VPath(copyPath);
+ mChildren.add(newPath);
+ if (newPath.mPathName != null) {
+ targetsMap.put(newPath.mPathName, newPath);
+ }
}
}
-
- for (int i = 0; i < copy.mChildGroupList.size(); i ++) {
- VGroup currentGroup = copy.mChildGroupList.get(i);
- mChildGroupList.add(new VGroup(currentGroup, targetsMap));
- }
}
public VGroup() {
@@ -922,10 +927,6 @@
return mLocalMatrix;
}
- public void add(VPath path) {
- mPathList.add(path);
- }
-
public boolean canApplyTheme() {
return mThemeAttrs != null;
}
@@ -980,15 +981,6 @@
mLocalMatrix.postRotate(mRotate, 0, 0);
mLocalMatrix.postTranslate(mTranslateX + mPivotX, mTranslateY + mPivotY);
}
-
- /**
- * Must return in order of adding
- * @return ordered list of paths
- */
- public ArrayList<VPath> getPaths() {
- return mPathList;
- }
-
}
private static class VPath {
@@ -1012,7 +1004,7 @@
float mStrokeMiterlimit = 4;
private PathParser.PathDataNode[] mNodes = null;
- private String mPathName;
+ String mPathName;
private int mChangingConfigurations;
public VPath() {
diff --git a/packages/SystemUI/res/layout/zen_mode_panel.xml b/packages/SystemUI/res/layout/zen_mode_panel.xml
index 9d959fa..16ee5c8 100644
--- a/packages/SystemUI/res/layout/zen_mode_panel.xml
+++ b/packages/SystemUI/res/layout/zen_mode_panel.xml
@@ -79,15 +79,4 @@
android:orientation="vertical"
android:paddingTop="3dp" />
- <TextView
- android:id="@+id/zen_alarm_warning"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingBottom="8dp"
- android:paddingLeft="@dimen/qs_panel_padding"
- android:paddingRight="@dimen/qs_panel_padding"
- android:paddingTop="8dp"
- android:text="@string/zen_alarm_warning"
- android:textAppearance="@style/TextAppearance.QS.Warning" />
-
</com.android.systemui.volume.ZenModePanel>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index ff60f1d..848fdf8 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -595,8 +595,8 @@
<!-- Description of the left direction in which one can to slide the handle in the Slide unlock screen. [CHAR LIMIT=NONE] -->
<string name="description_direction_left">"Slide left for <xliff:g id="target_description" example="Unlock">%s</xliff:g>.</string>
- <!-- Zen mode: Alarm warning. [CHAR LIMIT=40] -->
- <string name="zen_alarm_warning">You won\'t hear alarms or timers</string>
+ <!-- Zen mode: No interruptions title, with a warning about alarms and timers. [CHAR LIMIT=60] -->
+ <string name="zen_no_interruptions_with_warning">No interruptions, including alarms and timers</string>
<!-- Zen mode: No interruptions. [CHAR LIMIT=40] -->
<string name="zen_no_interruptions">No interruptions</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index d4acb11..f99b68b 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -185,11 +185,6 @@
<item name="android:textColor">@color/qs_subhead</item>
</style>
- <style name="TextAppearance.QS.Warning">
- <item name="android:textSize">12sp</item>
- <item name="android:textColor">@color/qs_subhead</item>
- </style>
-
<style name="TextAppearance.QS.SegmentedButton">
<item name="android:textSize">12sp</item>
<item name="android:textAllCaps">true</item>
@@ -259,11 +254,6 @@
<item name="android:layout_width">match_parent</item>
</style>
- <style name="QSWhiteTheme" parent="@android:style/Theme.DeviceDefault">
- <item name="android:colorControlNormal">#ffffffff</item>
- <item name="android:colorControlActivated">#ffffffff</item>
- </style>
-
<style name="QSBorderlessButton">
<item name="android:padding">12dp</item>
<item name="android:background">@drawable/btn_borderless_rect</item>
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
index b9d07d5..9bc00a9 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
@@ -85,7 +85,6 @@
private TextView mZenSubheadExpanded;
private View mMoreSettings;
private LinearLayout mZenConditions;
- private View mAlarmWarning;
private Callback mCallback;
private ZenModeController mController;
@@ -100,7 +99,7 @@
super(context, attrs);
mContext = context;
mFavorites = new Favorites();
- mInflater = LayoutInflater.from(new ContextThemeWrapper(context, R.style.QSWhiteTheme));
+ mInflater = LayoutInflater.from(mContext.getApplicationContext());
mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(mContext,
android.R.interpolator.fast_out_slow_in);
updateTag();
@@ -152,8 +151,6 @@
});
mZenConditions = (LinearLayout) findViewById(R.id.zen_conditions);
-
- mAlarmWarning = findViewById(R.id.zen_alarm_warning);
}
@Override
@@ -279,10 +276,9 @@
mZenSubheadCollapsed.setVisibility(!mExpanded ? VISIBLE : GONE);
mMoreSettings.setVisibility(zenImportant && mExpanded ? VISIBLE : GONE);
mZenConditions.setVisibility(!zenOff && mExpanded ? VISIBLE : GONE);
- mAlarmWarning.setVisibility(zenNone && mExpanded ? VISIBLE : GONE);
if (zenNone) {
- mZenSubheadExpanded.setText(R.string.zen_no_interruptions);
+ mZenSubheadExpanded.setText(R.string.zen_no_interruptions_with_warning);
mZenSubheadCollapsed.setText(mExitConditionText);
} else if (zenImportant) {
mZenSubheadExpanded.setText(R.string.zen_important_interruptions);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 5a331ac..e8e1536 100755
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -17047,22 +17047,34 @@
try {
Intent intent;
if (oldUserId >= 0) {
- intent = new Intent(Intent.ACTION_USER_BACKGROUND);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
- | Intent.FLAG_RECEIVER_FOREGROUND);
- intent.putExtra(Intent.EXTRA_USER_HANDLE, oldUserId);
- broadcastIntentLocked(null, null, intent,
- null, null, 0, null, null, null, AppOpsManager.OP_NONE,
- false, false, MY_PID, Process.SYSTEM_UID, oldUserId);
+ // Send USER_BACKGROUND broadcast to all profiles of the outgoing user
+ List<UserInfo> profiles = mUserManager.getProfiles(oldUserId, false);
+ int count = profiles.size();
+ for (int i = 0; i < count; i++) {
+ int profileUserId = profiles.get(i).id;
+ intent = new Intent(Intent.ACTION_USER_BACKGROUND);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
+ | Intent.FLAG_RECEIVER_FOREGROUND);
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, profileUserId);
+ broadcastIntentLocked(null, null, intent,
+ null, null, 0, null, null, null, AppOpsManager.OP_NONE,
+ false, false, MY_PID, Process.SYSTEM_UID, profileUserId);
+ }
}
if (newUserId >= 0) {
- intent = new Intent(Intent.ACTION_USER_FOREGROUND);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
- | Intent.FLAG_RECEIVER_FOREGROUND);
- intent.putExtra(Intent.EXTRA_USER_HANDLE, newUserId);
- broadcastIntentLocked(null, null, intent,
- null, null, 0, null, null, null, AppOpsManager.OP_NONE,
- false, false, MY_PID, Process.SYSTEM_UID, newUserId);
+ // Send USER_FOREGROUND broadcast to all profiles of the incoming user
+ List<UserInfo> profiles = mUserManager.getProfiles(newUserId, false);
+ int count = profiles.size();
+ for (int i = 0; i < count; i++) {
+ int profileUserId = profiles.get(i).id;
+ intent = new Intent(Intent.ACTION_USER_FOREGROUND);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
+ | Intent.FLAG_RECEIVER_FOREGROUND);
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, profileUserId);
+ broadcastIntentLocked(null, null, intent,
+ null, null, 0, null, null, null, AppOpsManager.OP_NONE,
+ false, false, MY_PID, Process.SYSTEM_UID, profileUserId);
+ }
intent = new Intent(Intent.ACTION_USER_SWITCHED);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
| Intent.FLAG_RECEIVER_FOREGROUND);
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index a5a502a..b1496cc 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -276,6 +276,12 @@
nativeClearLogicalAddress(mNativePtr);
}
+ @ServiceThreadOnly
+ void clearLocalDevices() {
+ assertRunOnServiceThread();
+ mLocalDevices.clear();
+ }
+
/**
* Return the physical address of the device.
*
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index 0cae459..07a564d 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -17,7 +17,9 @@
package com.android.server.hdmi;
import android.hardware.hdmi.HdmiCecDeviceInfo;
+import android.os.Handler;
import android.os.Looper;
+import android.os.Message;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
@@ -35,6 +37,11 @@
abstract class HdmiCecLocalDevice {
private static final String TAG = "HdmiCecLocalDevice";
+ private static final int MSG_DISABLE_DEVICE_TIMEOUT = 1;
+ // Timeout in millisecond for device clean up (5s).
+ // Normal actions timeout is 2s but some of them would have several sequence of timeout.
+ private static final int DEVICE_CLEANUP_TIMEOUT = 5000;
+
protected final HdmiControlService mService;
protected final int mDeviceType;
protected int mAddress;
@@ -58,6 +65,27 @@
// Note that access to this collection should happen in service thread.
private final LinkedList<FeatureAction> mActions = new LinkedList<>();
+ private Handler mHandler = new Handler () {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_DISABLE_DEVICE_TIMEOUT:
+ handleDisableDeviceTimeout();
+ break;
+ }
+ }
+ };
+
+ /**
+ * A callback interface to get notified when all pending action is cleared.
+ * It can be called when timeout happened.
+ */
+ interface PendingActionClearedCallback {
+ void onCleared(HdmiCecLocalDevice device);
+ }
+
+ protected PendingActionClearedCallback mPendingActionClearedCallback;
+
protected HdmiCecLocalDevice(HdmiControlService service, int deviceType) {
mService = service;
mDeviceType = deviceType;
@@ -482,10 +510,11 @@
}
protected void checkIfPendingActionsCleared() {
- if (mActions.isEmpty()) {
- mService.onPendingActionsCleared();
+ if (mActions.isEmpty() && mPendingActionClearedCallback != null) {
+ mPendingActionClearedCallback.onCleared(this);
}
}
+
protected void assertRunOnServiceThread() {
if (Looper.myLooper() != mService.getServiceLooper()) {
throw new IllegalStateException("Should run on service thread.");
@@ -578,21 +607,48 @@
}
/**
- * Called when the system started transition to standby mode.
- *
- * @param initiatedByCec true if this power sequence is initiated
- * by the reception the CEC messages like <StandBy>
- */
- protected void onTransitionToStandby(boolean initiatedByCec) {
- // If there are no outstanding actions, we'll go to STANDBY state.
- checkIfPendingActionsCleared();
- }
-
- /**
* Called when the system goes to standby mode.
*
* @param initiatedByCec true if this power sequence is initiated
- * by the reception the CEC messages like <StandBy>
+ * by the reception the CEC messages like <Standby>
*/
- protected void onStandBy(boolean initiatedByCec) {}
+ protected void onStandby(boolean initiatedByCec) {}
+
+ /**
+ * Disable device. {@code callback} is used to get notified when all pending
+ * actions are completed or timeout is issued.
+ *
+ * @param initiatedByCec true if this sequence is initiated
+ * by the reception the CEC messages like <Standby>
+ * @param callback callback interface to get notified when all pending actions are cleared
+ */
+ protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
+ mPendingActionClearedCallback = callback;
+ mHandler.sendMessageDelayed(Message.obtain(mHandler, MSG_DISABLE_DEVICE_TIMEOUT),
+ DEVICE_CLEANUP_TIMEOUT);
+ }
+
+ @ServiceThreadOnly
+ private void handleDisableDeviceTimeout() {
+ assertRunOnServiceThread();
+
+ // If all actions are not cleared in DEVICE_CLEANUP_TIMEOUT, enforce to finish them.
+ // onCleard will be called at the last action's finish method.
+ Iterator<FeatureAction> iter = mActions.iterator();
+ while (iter.hasNext()) {
+ FeatureAction action = iter.next();
+ action.finish();
+ iter.remove();
+ }
+ }
+
+ /**
+ * Send a key event to other device.
+ *
+ * @param keyCode key code defined in {@link android.view.KeyEvent}
+ * @param isPressed {@code true} for key down event
+ */
+ protected void sendKeyEvent(int keyCode, boolean isPressed) {
+ Slog.w(TAG, "sendKeyEvent not implemented");
+ }
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index 7ab2e8c..9807659 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -141,7 +141,9 @@
@Override
@ServiceThreadOnly
- protected void onTransitionToStandby(boolean initiatedByCec) {
+ protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
+ super.disableDevice(initiatedByCec, callback);
+
assertRunOnServiceThread();
if (!initiatedByCec && mIsActiveSource) {
mService.sendCecCommand(HdmiCecMessageBuilder.buildInactiveSource(
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 4185d25..c8c7401 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -224,10 +224,11 @@
* Sends key to a target CEC device.
*
* @param keyCode key code to send. Defined in {@link android.view.KeyEvent}.
- * @param isPressed true if this is keypress event
+ * @param isPressed true if this is key press event
*/
+ @Override
@ServiceThreadOnly
- void sendKeyEvent(int keyCode, boolean isPressed) {
+ protected void sendKeyEvent(int keyCode, boolean isPressed) {
assertRunOnServiceThread();
List<SendKeyAction> action = getActions(SendKeyAction.class);
if (!action.isEmpty()) {
@@ -1033,19 +1034,58 @@
@Override
@ServiceThreadOnly
- protected void onTransitionToStandby(boolean initiatedByCec) {
+ protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
+ super.disableDevice(initiatedByCec, callback);
assertRunOnServiceThread();
// Remove any repeated working actions.
// HotplugDetectionAction will be reinstated during the wake up process.
// HdmiControlService.onWakeUp() -> initializeLocalDevices() ->
// LocalDeviceTv.onAddressAllocated() -> launchDeviceDiscovery().
+ removeAction(DeviceDiscoveryAction.class);
removeAction(HotplugDetectionAction.class);
+
+ disableSystemAudioIfExist();
+ disableArcIfExist();
checkIfPendingActionsCleared();
}
+ @ServiceThreadOnly
+ private void disableSystemAudioIfExist() {
+ assertRunOnServiceThread();
+ if (getAvrDeviceInfo() == null) {
+ return;
+ }
+
+ // Seq #31.
+ removeAction(SystemAudioActionFromAvr.class);
+ removeAction(SystemAudioActionFromTv.class);
+ removeAction(SystemAudioAutoInitiationAction.class);
+ removeAction(SystemAudioStatusAction.class);
+ removeAction(VolumeControlAction.class);
+
+ // Once adding additional param which describes whether to record it to NVM or not to this
+ // method, put "false" for it.
+ setSystemAudioMode(false);
+ }
+
+ @ServiceThreadOnly
+ private void disableArcIfExist() {
+ assertRunOnServiceThread();
+ HdmiCecDeviceInfo avr = getAvrDeviceInfo();
+ if (avr == null) {
+ return;
+ }
+
+ // Seq #44.
+ removeAction(RequestArcInitiationAction.class);
+ if (!hasAction(RequestArcTerminationAction.class) && isArcEstabilished()) {
+ addAndStartAction(new RequestArcTerminationAction(this, avr.getLogicalAddress()));
+ }
+ }
+
@Override
@ServiceThreadOnly
- protected void onStandBy(boolean initiatedByCec) {
+ protected void onStandby(boolean initiatedByCec) {
assertRunOnServiceThread();
// Seq #11
if (!mService.isControlEnabled()) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
index 57f89b1..0b6c3c5 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
@@ -274,7 +274,7 @@
* @return newly created {@link HdmiCecMessage}
*/
static HdmiCecMessage buildInactiveSource(int src, int physicalAddress) {
- return buildCommand(src, Constants.ADDR_BROADCAST,
+ return buildCommand(src, Constants.ADDR_TV,
Constants.MESSAGE_INACTIVE_SOURCE, physicalAddressToParam(physicalAddress));
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index d35f03d..b0155f5 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -50,6 +50,7 @@
import com.android.server.SystemService;
import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback;
+import com.android.server.hdmi.HdmiCecLocalDevice.PendingActionClearedCallback;
import java.util.ArrayList;
import java.util.Collections;
@@ -72,9 +73,11 @@
* Called when {@link HdmiControlService#sendCecCommand} is completed.
*
* @param error result of send request.
- * @see {@link #SEND_RESULT_SUCCESS}
- * @see {@link #SEND_RESULT_NAK}
- * @see {@link #SEND_RESULT_FAILURE}
+ * <ul>
+ * <li>{@link Constants#SEND_RESULT_SUCCESS}
+ * <li>{@link Constants#SEND_RESULT_NAK}
+ * <li>{@link Constants#SEND_RESULT_FAILURE}
+ * </ul>
*/
void onSendCompleted(int error);
}
@@ -691,19 +694,17 @@
}
@Override
- public void sendKeyEvent(final int keyCode, final boolean isPressed) {
+ public void sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed) {
enforceAccessPermission();
runOnServiceThread(new Runnable() {
@Override
public void run() {
- // TODO: sendKeyEvent is for TV device only for now. Allow other
- // local devices of different types to use this as well.
- HdmiCecLocalDeviceTv tv = tv();
- if (tv == null) {
- Slog.w(TAG, "Local tv device not available");
+ HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType);
+ if (localDevice == null) {
+ Slog.w(TAG, "Local device not available");
return;
}
- tv.sendKeyEvent(keyCode, isPressed);
+ localDevice.sendKeyEvent(keyCode, isPressed);
}
});
}
@@ -841,27 +842,11 @@
@Override
public void setControlEnabled(final boolean enabled) {
enforceAccessPermission();
- synchronized (mLock) {
- mHdmiControlEnabled = enabled;
- }
- // TODO: Stop the running actions when disabled, and start
- // address allocation/device discovery when enabled.
- if (!enabled) {
- return;
- }
runOnServiceThread(new Runnable() {
@Override
public void run() {
- HdmiCecLocalDeviceTv tv = tv();
- if (tv == null) {
- return;
- }
- int value = enabled ? HdmiTvClient.ENABLED : HdmiTvClient.DISABLED;
- mCecController.setOption(HdmiTvClient.OPTION_CEC_ENABLE, value);
- if (mMhlController != null) {
- mMhlController.setOption(HdmiTvClient.OPTION_MHL_ENABLE, value);
- }
- tv.launchRoutingControl(false);
+ handleHdmiControlStatusChanged(enabled);
+
}
});
}
@@ -1241,25 +1226,48 @@
private void onStandby() {
assertRunOnServiceThread();
mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
+
+ final List<HdmiCecLocalDevice> devices = getAllLocalDevices();
+ disableDevices(new PendingActionClearedCallback() {
+ @Override
+ public void onCleared(HdmiCecLocalDevice device) {
+ Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType);
+ devices.remove(device);
+ if (devices.isEmpty()) {
+ clearLocalDevices();
+ onStandbyCompleted();
+ }
+ }
+ });
+ }
+
+ private void disableDevices(PendingActionClearedCallback callback) {
for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
- device.onTransitionToStandby(mStandbyMessageReceived);
+ device.disableDevice(mStandbyMessageReceived, callback);
}
}
- /**
- * Called when there are the outstanding actions in the local devices.
- * This callback is used to wait for when the action queue is empty
- * during the power state transition to standby.
- */
@ServiceThreadOnly
- void onPendingActionsCleared() {
+ private void clearLocalDevices() {
assertRunOnServiceThread();
+ if (mCecController == null) {
+ return;
+ }
+ mCecController.clearLogicalAddress();
+ mCecController.clearLocalDevices();
+ }
+
+ @ServiceThreadOnly
+ private void onStandbyCompleted() {
+ assertRunOnServiceThread();
+ Slog.v(TAG, "onStandbyCompleted");
+
if (mPowerStatus != HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) {
return;
}
mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
- device.onStandBy(mStandbyMessageReceived);
+ device.onStandby(mStandbyMessageReceived);
}
mStandbyMessageReceived = false;
mCecController.setOption(HdmiTvClient.OPTION_CEC_SERVICE_CONTROL, HdmiTvClient.DISABLED);
@@ -1305,4 +1313,36 @@
mProhibitMode = enabled;
}
}
+
+ @ServiceThreadOnly
+ private void handleHdmiControlStatusChanged(boolean enabled) {
+ assertRunOnServiceThread();
+
+ int value = enabled ? HdmiTvClient.ENABLED : HdmiTvClient.DISABLED;
+ mCecController.setOption(HdmiTvClient.OPTION_CEC_ENABLE, value);
+ if (mMhlController != null) {
+ mMhlController.setOption(HdmiTvClient.OPTION_MHL_ENABLE, value);
+ }
+
+ synchronized (mLock) {
+ mHdmiControlEnabled = enabled;
+ }
+
+ if (enabled) {
+ // TODO: call initalizedLocalDevice with additional param once putting
+ // it to address allocation result.
+ HdmiCecLocalDeviceTv tv = tv();
+ if (tv != null) {
+ tv.launchRoutingControl(false);
+ }
+ } else {
+ disableDevices(new PendingActionClearedCallback() {
+ @Override
+ public void onCleared(HdmiCecLocalDevice device) {
+ assertRunOnServiceThread();
+ clearLocalDevices();
+ }
+ });
+ }
+ }
}
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index cab2728..7f8b232 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -140,12 +140,10 @@
* This cancels the job if it's already been scheduled, and replaces it with the one provided.
* @param job JobInfo object containing execution parameters
* @param uId The package identifier of the application this job is for.
- * @param canPersistJob Whether or not the client has the appropriate permissions for
- * persisting this job.
* @return Result of this operation. See <code>JobScheduler#RESULT_*</code> return codes.
*/
- public int schedule(JobInfo job, int uId, boolean canPersistJob) {
- JobStatus jobStatus = new JobStatus(job, uId, canPersistJob);
+ public int schedule(JobInfo job, int uId) {
+ JobStatus jobStatus = new JobStatus(job, uId);
cancelJob(uId, job.getId());
startTrackingJob(jobStatus);
return JobScheduler.RESULT_SUCCESS;
@@ -668,11 +666,16 @@
final int uid = Binder.getCallingUid();
enforceValidJobRequest(uid, job);
- final boolean canPersist = canPersistJobs(pid, uid);
+ if (job.isPersisted()) {
+ if (!canPersistJobs(pid, uid)) {
+ throw new IllegalArgumentException("Error: requested job be persisted without"
+ + " holding RECEIVE_BOOT_COMPLETED permission.");
+ }
+ }
long ident = Binder.clearCallingIdentity();
try {
- return JobSchedulerService.this.schedule(job, uid, canPersist);
+ return JobSchedulerService.this.schedule(job, uid);
} finally {
Binder.restoreCallingIdentity(ident);
}
diff --git a/services/core/java/com/android/server/job/JobStore.java b/services/core/java/com/android/server/job/JobStore.java
index 4ac26c1..8736980 100644
--- a/services/core/java/com/android/server/job/JobStore.java
+++ b/services/core/java/com/android/server/job/JobStore.java
@@ -160,7 +160,9 @@
}
return false;
}
- maybeWriteStatusToDiskAsync();
+ if (jobStatus.isPersisted()) {
+ maybeWriteStatusToDiskAsync();
+ }
return removed;
}
@@ -429,7 +431,8 @@
}
}
- private List<JobStatus> readJobMapImpl(FileInputStream fis) throws XmlPullParserException, IOException {
+ private List<JobStatus> readJobMapImpl(FileInputStream fis)
+ throws XmlPullParserException, IOException {
XmlPullParser parser = Xml.newPullParser();
parser.setInput(fis, null);
@@ -498,6 +501,7 @@
// Read out job identifier attributes.
try {
jobBuilder = buildBuilderFromXml(parser);
+ jobBuilder.setIsPersisted(true);
uid = Integer.valueOf(parser.getAttributeValue(null, "uid"));
} catch (NumberFormatException e) {
Slog.e(TAG, "Error parsing job's required fields, skipping");
diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java
index 53337c4..9ee2869 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -43,9 +43,6 @@
final JobInfo job;
final int uId;
- /** At reschedule time we need to know whether to update job on disk. */
- final boolean persisted;
-
// Constraints.
final AtomicBoolean chargingConstraintSatisfied = new AtomicBoolean();
final AtomicBoolean timeDelayConstraintSatisfied = new AtomicBoolean();
@@ -72,16 +69,15 @@
return uId;
}
- private JobStatus(JobInfo job, int uId, boolean persisted, int numFailures) {
+ private JobStatus(JobInfo job, int uId, int numFailures) {
this.job = job;
this.uId = uId;
this.numFailures = numFailures;
- this.persisted = persisted;
}
/** Create a newly scheduled job. */
- public JobStatus(JobInfo job, int uId, boolean persisted) {
- this(job, uId, persisted, 0);
+ public JobStatus(JobInfo job, int uId) {
+ this(job, uId, 0);
final long elapsedNow = SystemClock.elapsedRealtime();
@@ -105,7 +101,7 @@
*/
public JobStatus(JobInfo job, int uId, long earliestRunTimeElapsedMillis,
long latestRunTimeElapsedMillis) {
- this(job, uId, true, 0);
+ this(job, uId, 0);
this.earliestRunTimeElapsedMillis = earliestRunTimeElapsedMillis;
this.latestRunTimeElapsedMillis = latestRunTimeElapsedMillis;
@@ -114,7 +110,7 @@
/** Create a new job to be rescheduled with the provided parameters. */
public JobStatus(JobStatus rescheduling, long newEarliestRuntimeElapsedMillis,
long newLatestRuntimeElapsedMillis, int backoffAttempt) {
- this(rescheduling.job, rescheduling.getUid(), rescheduling.isPersisted(), backoffAttempt);
+ this(rescheduling.job, rescheduling.getUid(), backoffAttempt);
earliestRunTimeElapsedMillis = newEarliestRuntimeElapsedMillis;
latestRunTimeElapsedMillis = newLatestRuntimeElapsedMillis;
@@ -172,6 +168,10 @@
return job.isRequireDeviceIdle();
}
+ public boolean isPersisted() {
+ return job.isPersisted();
+ }
+
public long getEarliestRunTime() {
return earliestRunTimeElapsedMillis;
}
@@ -180,9 +180,6 @@
return latestRunTimeElapsedMillis;
}
- public boolean isPersisted() {
- return persisted;
- }
/**
* @return Whether or not this job is ready to run, based on its requirements.
*/
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 71b8974..bb900db 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -3136,6 +3136,9 @@
FileUtils.setPermissions(path.toString(), FileUtils.S_IRWXU | FileUtils.S_IRWXG
| FileUtils.S_IXOTH, -1, -1);
for (PackageSetting ps : mPackages.values()) {
+ if (ps.pkg == null || ps.pkg.applicationInfo == null) {
+ continue;
+ }
// Only system apps are initially installed.
ps.setInstalled((ps.pkgFlags&ApplicationInfo.FLAG_SYSTEM) != 0, userHandle);
// Need to create a data directory for all apps under this user.
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 61f09f6..5445dc0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3592,6 +3592,30 @@
}
@Override
+ public boolean switchUser(ComponentName who, UserHandle userHandle) {
+ synchronized (this) {
+ if (who == null) {
+ throw new NullPointerException("ComponentName is null");
+ }
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+
+ long id = Binder.clearCallingIdentity();
+ try {
+ int userId = UserHandle.USER_OWNER;
+ if (userHandle != null) {
+ userId = userHandle.getIdentifier();
+ }
+ return ActivityManagerNative.getDefault().switchUser(userId);
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "Couldn't switch user", e);
+ return false;
+ } finally {
+ restoreCallingIdentity(id);
+ }
+ }
+ }
+
+ @Override
public Bundle getApplicationRestrictions(ComponentName who, String packageName) {
final UserHandle userHandle = new UserHandle(UserHandle.getCallingUserId());
diff --git a/services/tests/servicestests/src/com/android/server/task/TaskStoreTest.java b/services/tests/servicestests/src/com/android/server/task/TaskStoreTest.java
index 7a7fa07..cb8da70 100644
--- a/services/tests/servicestests/src/com/android/server/task/TaskStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/task/TaskStoreTest.java
@@ -63,8 +63,9 @@
.setBackoffCriteria(initialBackoff, JobInfo.BackoffPolicy.EXPONENTIAL)
.setOverrideDeadline(runByMillis)
.setMinimumLatency(runFromMillis)
+ .setIsPersisted(true)
.build();
- final JobStatus ts = new JobStatus(task, SOME_UID, true /* persisted */);
+ final JobStatus ts = new JobStatus(task, SOME_UID);
mTaskStoreUnderTest.add(ts);
Thread.sleep(IO_WAIT);
// Manually load tasks from xml file.
@@ -89,15 +90,17 @@
.setRequiresDeviceIdle(true)
.setPeriodic(10000L)
.setRequiresCharging(true)
+ .setIsPersisted(true)
.build();
final JobInfo task2 = new Builder(12, mComponent)
.setMinimumLatency(5000L)
.setBackoffCriteria(15000L, JobInfo.BackoffPolicy.LINEAR)
.setOverrideDeadline(30000L)
.setRequiredNetworkCapabilities(JobInfo.NetworkType.UNMETERED)
+ .setIsPersisted(true)
.build();
- final JobStatus taskStatus1 = new JobStatus(task1, SOME_UID, true /* persisted */);
- final JobStatus taskStatus2 = new JobStatus(task2, SOME_UID, true /* persisted */);
+ final JobStatus taskStatus1 = new JobStatus(task1, SOME_UID);
+ final JobStatus taskStatus2 = new JobStatus(task2, SOME_UID);
mTaskStoreUnderTest.add(taskStatus1);
mTaskStoreUnderTest.add(taskStatus2);
Thread.sleep(IO_WAIT);
@@ -128,7 +131,8 @@
JobInfo.Builder b = new Builder(8, mComponent)
.setRequiresDeviceIdle(true)
.setPeriodic(10000L)
- .setRequiresCharging(true);
+ .setRequiresCharging(true)
+ .setIsPersisted(true);
PersistableBundle extras = new PersistableBundle();
extras.putDouble("hello", 3.2);
@@ -136,7 +140,7 @@
extras.putInt("into", 3);
b.setExtras(extras);
final JobInfo task = b.build();
- JobStatus taskStatus = new JobStatus(task, SOME_UID, true /* persisted */);
+ JobStatus taskStatus = new JobStatus(task, SOME_UID);
mTaskStoreUnderTest.add(taskStatus);
Thread.sleep(IO_WAIT);
diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable25.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable25.xml
new file mode 100644
index 0000000..a3f0447
--- /dev/null
+++ b/tests/VectorDrawableTest/res/drawable/vector_drawable25.xml
@@ -0,0 +1,93 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <size
+ android:height="64dp"
+ android:width="64dp" />
+
+ <viewport
+ android:viewportHeight="400"
+ android:viewportWidth="400" />
+
+ <group
+ android:name="FirstLevelGroup"
+ android:alpha="0.9"
+ android:translateX="100.0"
+ android:translateY="0.0" >
+ <group
+ android:name="SecondLevelGroup1"
+ android:alpha="0.9"
+ android:translateX="-100.0"
+ android:translateY="50.0" >
+ <path
+ android:fill="#FF00FF00"
+ android:pathData="@string/rectangle200" />
+
+ <group
+ android:name="ThridLevelGroup1"
+ android:alpha="0.9"
+ android:translateX="-100.0"
+ android:translateY="50.0" >
+ <path
+ android:fill="#FF0000FF"
+ android:pathData="@string/rectangle200" />
+ </group>
+ <group
+ android:name="ThridLevelGroup2"
+ android:alpha="0.8"
+ android:translateX="100.0"
+ android:translateY="50.0" >
+ <path
+ android:fill="#FF000000"
+ android:pathData="@string/rectangle200" />
+ </group>
+ </group>
+ <group
+ android:name="SecondLevelGroup2"
+ android:alpha="0.8"
+ android:translateX="100.0"
+ android:translateY="50.0" >
+ <path
+ android:fill="#FF0000FF"
+ android:pathData="@string/rectangle200" />
+
+ <group
+ android:name="ThridLevelGroup3"
+ android:alpha="0.9"
+ android:translateX="-100.0"
+ android:translateY="50.0" >
+ <path
+ android:fill="#FFFF0000"
+ android:pathData="@string/rectangle200" />
+ </group>
+ <group
+ android:name="ThridLevelGroup4"
+ android:alpha="0.8"
+ android:translateX="100.0"
+ android:translateY="50.0" >
+ <path
+ android:fill="#FF00FF00"
+ android:pathData="@string/rectangle200" />
+ </group>
+ </group>
+
+ <path
+ android:fill="#FFFF0000"
+ android:pathData="@string/rectangle200" />
+ </group>
+
+</vector>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/values/strings.xml b/tests/VectorDrawableTest/res/values/strings.xml
index 54cffa8..a550549 100644
--- a/tests/VectorDrawableTest/res/values/strings.xml
+++ b/tests/VectorDrawableTest/res/values/strings.xml
@@ -24,5 +24,5 @@
<string name="equal2"> "M300,35 l 0,-35 70,0 0,35z M300,105 l 70,0 0,35 -70,0z"</string>
<string name="round_box">"m2.10001,-6c-1.9551,0 -0.5,0.02499 -2.10001,0.02499c-1.575,0 0.0031,-0.02499 -1.95,-0.02499c-2.543,0 -4,2.2816 -4,4.85001c0,3.52929 0.25,6.25 5.95,6.25c5.7,0 6,-2.72071 6,-6.25c0,-2.56841 -1.35699,-4.85001 -3.89999,-4.85001"</string>
<string name="heart"> "m4.5,-7c-1.95509,0 -3.83009,1.26759 -4.5,3c-0.66991,-1.73241 -2.54691,-3 -4.5,-3c-2.543,0 -4.5,1.93159 -4.5,4.5c0,3.5293 3.793,6.2578 9,11.5c5.207,-5.2422 9,-7.9707 9,-11.5c0,-2.56841 -1.957,-4.5 -4.5,-4.5"</string>
-
+ <string name="rectangle200">"M 0,0 l 200,0 l 0, 200 l -200, 0 z"</string>
</resources>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/src/com/android/test/dynamic/VectorDrawablePerformance.java b/tests/VectorDrawableTest/src/com/android/test/dynamic/VectorDrawablePerformance.java
index 814deb8..e8b6952 100644
--- a/tests/VectorDrawableTest/src/com/android/test/dynamic/VectorDrawablePerformance.java
+++ b/tests/VectorDrawableTest/src/com/android/test/dynamic/VectorDrawablePerformance.java
@@ -52,6 +52,7 @@
R.drawable.vector_drawable22,
R.drawable.vector_drawable23,
R.drawable.vector_drawable24,
+ R.drawable.vector_drawable25,
};
@Override