diff options
Diffstat (limited to 'WallpaperPicker/src')
44 files changed, 1405 insertions, 8370 deletions
diff --git a/WallpaperPicker/src/com/android/gallery3d/common/BitmapCropTask.java b/WallpaperPicker/src/com/android/gallery3d/common/BitmapCropTask.java index 8b5144748..212fb84ea 100644 --- a/WallpaperPicker/src/com/android/gallery3d/common/BitmapCropTask.java +++ b/WallpaperPicker/src/com/android/gallery3d/common/BitmapCropTask.java @@ -126,7 +126,7 @@ public class BitmapCropTask extends AsyncTask<Void, Void, Boolean> { mNoCrop = value; } - public void setOnEndRunnable(OnEndCropHandler onEndCropHandler) { + public void setOnEndCropHandler(OnEndCropHandler onEndCropHandler) { mOnEndCropHandler = onEndCropHandler; } diff --git a/WallpaperPicker/src/com/android/gallery3d/common/BitmapUtils.java b/WallpaperPicker/src/com/android/gallery3d/common/BitmapUtils.java index 9ac5c1bf7..6ddda87b5 100644 --- a/WallpaperPicker/src/com/android/gallery3d/common/BitmapUtils.java +++ b/WallpaperPicker/src/com/android/gallery3d/common/BitmapUtils.java @@ -21,8 +21,6 @@ import android.content.res.Resources; import android.net.Uri; import android.util.Log; -import com.android.gallery3d.exif.ExifInterface; - import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; @@ -42,38 +40,26 @@ public class BitmapUtils { } public static int getRotationFromExif(Context context, Uri uri) { - return BitmapUtils.getRotationFromExifHelper(null, 0, context, uri); + return BitmapUtils.getRotationFromExifHelper(null, 0, uri, context); } - public static int getRotationFromExif(Resources res, int resId) { - return BitmapUtils.getRotationFromExifHelper(res, resId, null, null); + public static int getRotationFromExif(Resources res, int resId, Context context) { + return BitmapUtils.getRotationFromExifHelper(res, resId, null, context); } - private static int getRotationFromExifHelper(Resources res, int resId, Context context, Uri uri) { - ExifInterface ei = new ExifInterface(); + private static int getRotationFromExifHelper(Resources res, int resId, + Uri uri, Context context) { InputStream is = null; - BufferedInputStream bis = null; try { if (uri != null) { is = context.getContentResolver().openInputStream(uri); - bis = new BufferedInputStream(is); - ei.readExif(bis); } else { is = res.openRawResource(resId); - bis = new BufferedInputStream(is); - ei.readExif(bis); - } - Integer ori = ei.getTagIntValue(ExifInterface.TAG_ORIENTATION); - if (ori != null) { - return ExifInterface.getRotationForOrientationValue(ori.shortValue()); } - } catch (IOException e) { - Log.w(TAG, "Getting exif data failed", e); - } catch (NullPointerException e) { - // Sometimes the ExifInterface has an internal NPE if Exif data isn't valid + return ExifOrientation.readRotation(new BufferedInputStream(is), context); + } catch (IOException | NullPointerException e) { Log.w(TAG, "Getting exif data failed", e); } finally { - Utils.closeSilently(bis); Utils.closeSilently(is); } return 0; diff --git a/WallpaperPicker/src/com/android/gallery3d/common/ExifOrientation.java b/WallpaperPicker/src/com/android/gallery3d/common/ExifOrientation.java new file mode 100644 index 000000000..ad4370c37 --- /dev/null +++ b/WallpaperPicker/src/com/android/gallery3d/common/ExifOrientation.java @@ -0,0 +1,145 @@ +/** + * Copyright (C) 2015 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 com.android.gallery3d.common; + +import android.content.Context; +import android.media.ExifInterface; +import android.util.Log; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +public class ExifOrientation { + private static final String TAG = "ExifOrientation"; + private static final boolean DEBUG = false; + + private static final short SOI = (short) 0xFFD8; // start of input + private static final short APP0 = (short) 0xFFE0; + private static final short APPF = (short) 0xFFEF; + private static final short APP1 = (short) 0xFFE1; + private static final short SOS = (short) 0xFFDA; // start of stream + private static final short EOI = (short) 0xFFD9; // end of input + + // The header is available in first 64 bytes, so reading upto 128 bytes + // should be more than enough. + private static final int MAX_BYTES_TO_READ = 128 * 1024; + + /** + * Parses the rotation of the JPEG image from the input stream. + */ + public static final int readRotation(InputStream in, Context context) { + // Since the platform implementation only takes file input, create a temporary file + // with just the image header. + File tempFile = null; + DataOutputStream tempOut = null; + + try { + DataInputStream din = new DataInputStream(in); + int pos = 0; + if (din.readShort() == SOI) { + pos += 2; + + short marker = din.readShort(); + pos += 2; + + while ((marker >= APP0 && marker <= APPF) && pos < MAX_BYTES_TO_READ) { + int length = din.readUnsignedShort(); + if (length < 2) { + throw new IOException("Invalid header size"); + } + + // We only want APP1 headers + if (length > 2) { + if (marker == APP1) { + // Copy the header + if (tempFile == null) { + tempFile = File.createTempFile(TAG, ".jpg", context.getCacheDir()); + tempOut = new DataOutputStream(new FileOutputStream(tempFile)); + tempOut.writeShort(SOI); + } + + tempOut.writeShort(marker); + tempOut.writeShort(length); + + byte[] header = new byte[length - 2]; + din.read(header); + tempOut.write(header); + } else { + din.skip(length - 2); + } + } + pos += length; + + marker = din.readShort(); + pos += 2; + } + + if (tempOut != null) { + // Write empty image data. + tempOut.writeShort(SOS); + // Write the frame size as 2. Since this includes the size bytes as well + // (short = 2 bytes), it implies there is 0 byte of image data. + tempOut.writeShort(2); + + // End of input + tempOut.writeShort(EOI); + tempOut.close(); + + return readRotation(tempFile.getAbsolutePath()); + } + } + } catch (IOException e) { + if (DEBUG) { + Log.d(TAG, "Error parsing input stream", e); + } + } finally { + Utils.closeSilently(in); + Utils.closeSilently(tempOut); + if (tempFile != null) { + tempFile.delete(); + } + } + return 0; + } + + /** + * Parses the rotation of the JPEG image. + */ + public static final int readRotation(String filePath) { + try { + ExifInterface exif = new ExifInterface(filePath); + switch (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0)) { + case ExifInterface.ORIENTATION_ROTATE_90: + return 90; + case ExifInterface.ORIENTATION_ROTATE_270: + return 270; + case ExifInterface.ORIENTATION_ROTATE_180: + return 180; + default: + return 0; + } + } catch (IOException e) { + if (DEBUG) { + Log.d(TAG, "Error reading file", e); + } + } + return 0; + } +} diff --git a/WallpaperPicker/src/com/android/gallery3d/exif/ByteBufferInputStream.java b/WallpaperPicker/src/com/android/gallery3d/exif/ByteBufferInputStream.java deleted file mode 100644 index 7fb9f22cc..000000000 --- a/WallpaperPicker/src/com/android/gallery3d/exif/ByteBufferInputStream.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.gallery3d.exif; - -import java.io.InputStream; -import java.nio.ByteBuffer; - -class ByteBufferInputStream extends InputStream { - - private ByteBuffer mBuf; - - public ByteBufferInputStream(ByteBuffer buf) { - mBuf = buf; - } - - @Override - public int read() { - if (!mBuf.hasRemaining()) { - return -1; - } - return mBuf.get() & 0xFF; - } - - @Override - public int read(byte[] bytes, int off, int len) { - if (!mBuf.hasRemaining()) { - return -1; - } - - len = Math.min(len, mBuf.remaining()); - mBuf.get(bytes, off, len); - return len; - } -} diff --git a/WallpaperPicker/src/com/android/gallery3d/exif/CountedDataInputStream.java b/WallpaperPicker/src/com/android/gallery3d/exif/CountedDataInputStream.java deleted file mode 100644 index dfd4a1a10..000000000 --- a/WallpaperPicker/src/com/android/gallery3d/exif/CountedDataInputStream.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.gallery3d.exif; - -import java.io.EOFException; -import java.io.FilterInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.charset.Charset; - -class CountedDataInputStream extends FilterInputStream { - - private int mCount = 0; - - // allocate a byte buffer for a long value; - private final byte mByteArray[] = new byte[8]; - private final ByteBuffer mByteBuffer = ByteBuffer.wrap(mByteArray); - - protected CountedDataInputStream(InputStream in) { - super(in); - } - - public int getReadByteCount() { - return mCount; - } - - @Override - public int read(byte[] b) throws IOException { - int r = in.read(b); - mCount += (r >= 0) ? r : 0; - return r; - } - - @Override - public int read(byte[] b, int off, int len) throws IOException { - int r = in.read(b, off, len); - mCount += (r >= 0) ? r : 0; - return r; - } - - @Override - public int read() throws IOException { - int r = in.read(); - mCount += (r >= 0) ? 1 : 0; - return r; - } - - @Override - public long skip(long length) throws IOException { - long skip = in.skip(length); - mCount += skip; - return skip; - } - - public void skipOrThrow(long length) throws IOException { - if (skip(length) != length) throw new EOFException(); - } - - public void skipTo(long target) throws IOException { - long cur = mCount; - long diff = target - cur; - assert(diff >= 0); - skipOrThrow(diff); - } - - public void readOrThrow(byte[] b, int off, int len) throws IOException { - int r = read(b, off, len); - if (r != len) throw new EOFException(); - } - - public void readOrThrow(byte[] b) throws IOException { - readOrThrow(b, 0, b.length); - } - - public void setByteOrder(ByteOrder order) { - mByteBuffer.order(order); - } - - public ByteOrder getByteOrder() { - return mByteBuffer.order(); - } - - public short readShort() throws IOException { - readOrThrow(mByteArray, 0 ,2); - mByteBuffer.rewind(); - return mByteBuffer.getShort(); - } - - public int readUnsignedShort() throws IOException { - return readShort() & 0xffff; - } - - public int readInt() throws IOException { - readOrThrow(mByteArray, 0 , 4); - mByteBuffer.rewind(); - return mByteBuffer.getInt(); - } - - public long readUnsignedInt() throws IOException { - return readInt() & 0xffffffffL; - } - - public long readLong() throws IOException { - readOrThrow(mByteArray, 0 , 8); - mByteBuffer.rewind(); - return mByteBuffer.getLong(); - } - - public String readString(int n) throws IOException { - byte buf[] = new byte[n]; - readOrThrow(buf); - return new String(buf, "UTF8"); - } - - public String readString(int n, Charset charset) throws IOException { - byte buf[] = new byte[n]; - readOrThrow(buf); - return new String(buf, charset); - } -}
\ No newline at end of file diff --git a/WallpaperPicker/src/com/android/gallery3d/exif/ExifData.java b/WallpaperPicker/src/com/android/gallery3d/exif/ExifData.java deleted file mode 100644 index 8422382bb..000000000 --- a/WallpaperPicker/src/com/android/gallery3d/exif/ExifData.java +++ /dev/null @@ -1,348 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.gallery3d.exif; - -import android.util.Log; - -import java.io.UnsupportedEncodingException; -import java.nio.ByteOrder; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * This class stores the EXIF header in IFDs according to the JPEG - * specification. It is the result produced by {@link ExifReader}. - * - * @see ExifReader - * @see IfdData - */ -class ExifData { - private static final String TAG = "ExifData"; - private static final byte[] USER_COMMENT_ASCII = { - 0x41, 0x53, 0x43, 0x49, 0x49, 0x00, 0x00, 0x00 - }; - private static final byte[] USER_COMMENT_JIS = { - 0x4A, 0x49, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00 - }; - private static final byte[] USER_COMMENT_UNICODE = { - 0x55, 0x4E, 0x49, 0x43, 0x4F, 0x44, 0x45, 0x00 - }; - - private final IfdData[] mIfdDatas = new IfdData[IfdId.TYPE_IFD_COUNT]; - private byte[] mThumbnail; - private ArrayList<byte[]> mStripBytes = new ArrayList<byte[]>(); - private final ByteOrder mByteOrder; - - ExifData(ByteOrder order) { - mByteOrder = order; - } - - /** - * Gets the compressed thumbnail. Returns null if there is no compressed - * thumbnail. - * - * @see #hasCompressedThumbnail() - */ - protected byte[] getCompressedThumbnail() { - return mThumbnail; - } - - /** - * Sets the compressed thumbnail. - */ - protected void setCompressedThumbnail(byte[] thumbnail) { - mThumbnail = thumbnail; - } - - /** - * Returns true it this header contains a compressed thumbnail. - */ - protected boolean hasCompressedThumbnail() { - return mThumbnail != null; - } - - /** - * Adds an uncompressed strip. - */ - protected void setStripBytes(int index, byte[] strip) { - if (index < mStripBytes.size()) { - mStripBytes.set(index, strip); - } else { - for (int i = mStripBytes.size(); i < index; i++) { - mStripBytes.add(null); - } - mStripBytes.add(strip); - } - } - - /** - * Gets the strip count. - */ - protected int getStripCount() { - return mStripBytes.size(); - } - - /** - * Gets the strip at the specified index. - * - * @exceptions #IndexOutOfBoundException - */ - protected byte[] getStrip(int index) { - return mStripBytes.get(index); - } - - /** - * Returns true if this header contains uncompressed strip. - */ - protected boolean hasUncompressedStrip() { - return mStripBytes.size() != 0; - } - - /** - * Gets the byte order. - */ - protected ByteOrder getByteOrder() { - return mByteOrder; - } - - /** - * Returns the {@link IfdData} object corresponding to a given IFD if it - * exists or null. - */ - protected IfdData getIfdData(int ifdId) { - if (ExifTag.isValidIfd(ifdId)) { - return mIfdDatas[ifdId]; - } - return null; - } - - /** - * Adds IFD data. If IFD data of the same type already exists, it will be - * replaced by the new data. - */ - protected void addIfdData(IfdData data) { - mIfdDatas[data.getId()] = data; - } - - /** - * Returns the {@link IfdData} object corresponding to a given IFD or - * generates one if none exist. - */ - protected IfdData getOrCreateIfdData(int ifdId) { - IfdData ifdData = mIfdDatas[ifdId]; - if (ifdData == null) { - ifdData = new IfdData(ifdId); - mIfdDatas[ifdId] = ifdData; - } - return ifdData; - } - - /** - * Returns the tag with a given TID in the given IFD if the tag exists. - * Otherwise returns null. - */ - protected ExifTag getTag(short tag, int ifd) { - IfdData ifdData = mIfdDatas[ifd]; - return (ifdData == null) ? null : ifdData.getTag(tag); - } - - /** - * Adds the given ExifTag to its default IFD and returns an existing ExifTag - * with the same TID or null if none exist. - */ - protected ExifTag addTag(ExifTag tag) { - if (tag != null) { - int ifd = tag.getIfd(); - return addTag(tag, ifd); - } - return null; - } - - /** - * Adds the given ExifTag to the given IFD and returns an existing ExifTag - * with the same TID or null if none exist. - */ - protected ExifTag addTag(ExifTag tag, int ifdId) { - if (tag != null && ExifTag.isValidIfd(ifdId)) { - IfdData ifdData = getOrCreateIfdData(ifdId); - return ifdData.setTag(tag); - } - return null; - } - - protected void clearThumbnailAndStrips() { - mThumbnail = null; - mStripBytes.clear(); - } - - /** - * Removes the thumbnail and its related tags. IFD1 will be removed. - */ - protected void removeThumbnailData() { - clearThumbnailAndStrips(); - mIfdDatas[IfdId.TYPE_IFD_1] = null; - } - - /** - * Removes the tag with a given TID and IFD. - */ - protected void removeTag(short tagId, int ifdId) { - IfdData ifdData = mIfdDatas[ifdId]; - if (ifdData == null) { - return; - } - ifdData.removeTag(tagId); - } - - /** - * Decodes the user comment tag into string as specified in the EXIF - * standard. Returns null if decoding failed. - */ - protected String getUserComment() { - IfdData ifdData = mIfdDatas[IfdId.TYPE_IFD_0]; - if (ifdData == null) { - return null; - } - ExifTag tag = ifdData.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_USER_COMMENT)); - if (tag == null) { - return null; - } - if (tag.getComponentCount() < 8) { - return null; - } - - byte[] buf = new byte[tag.getComponentCount()]; - tag.getBytes(buf); - - byte[] code = new byte[8]; - System.arraycopy(buf, 0, code, 0, 8); - - try { - if (Arrays.equals(code, USER_COMMENT_ASCII)) { - return new String(buf, 8, buf.length - 8, "US-ASCII"); - } else if (Arrays.equals(code, USER_COMMENT_JIS)) { - return new String(buf, 8, buf.length - 8, "EUC-JP"); - } else if (Arrays.equals(code, USER_COMMENT_UNICODE)) { - return new String(buf, 8, buf.length - 8, "UTF-16"); - } else { - return null; - } - } catch (UnsupportedEncodingException e) { - Log.w(TAG, "Failed to decode the user comment"); - return null; - } - } - - /** - * Returns a list of all {@link ExifTag}s in the ExifData or null if there - * are none. - */ - protected List<ExifTag> getAllTags() { - ArrayList<ExifTag> ret = new ArrayList<ExifTag>(); - for (IfdData d : mIfdDatas) { - if (d != null) { - ExifTag[] tags = d.getAllTags(); - if (tags != null) { - for (ExifTag t : tags) { - ret.add(t); - } - } - } - } - if (ret.size() == 0) { - return null; - } - return ret; - } - - /** - * Returns a list of all {@link ExifTag}s in a given IFD or null if there - * are none. - */ - protected List<ExifTag> getAllTagsForIfd(int ifd) { - IfdData d = mIfdDatas[ifd]; - if (d == null) { - return null; - } - ExifTag[] tags = d.getAllTags(); - if (tags == null) { - return null; - } - ArrayList<ExifTag> ret = new ArrayList<ExifTag>(tags.length); - for (ExifTag t : tags) { - ret.add(t); - } - if (ret.size() == 0) { - return null; - } - return ret; - } - - /** - * Returns a list of all {@link ExifTag}s with a given TID or null if there - * are none. - */ - protected List<ExifTag> getAllTagsForTagId(short tag) { - ArrayList<ExifTag> ret = new ArrayList<ExifTag>(); - for (IfdData d : mIfdDatas) { - if (d != null) { - ExifTag t = d.getTag(tag); - if (t != null) { - ret.add(t); - } - } - } - if (ret.size() == 0) { - return null; - } - return ret; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (obj instanceof ExifData) { - ExifData data = (ExifData) obj; - if (data.mByteOrder != mByteOrder || - data.mStripBytes.size() != mStripBytes.size() || - !Arrays.equals(data.mThumbnail, mThumbnail)) { - return false; - } - for (int i = 0; i < mStripBytes.size(); i++) { - if (!Arrays.equals(data.mStripBytes.get(i), mStripBytes.get(i))) { - return false; - } - } - for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) { - IfdData ifd1 = data.getIfdData(i); - IfdData ifd2 = getIfdData(i); - if (ifd1 != ifd2 && ifd1 != null && !ifd1.equals(ifd2)) { - return false; - } - } - return true; - } - return false; - } - -} diff --git a/WallpaperPicker/src/com/android/gallery3d/exif/ExifInterface.java b/WallpaperPicker/src/com/android/gallery3d/exif/ExifInterface.java deleted file mode 100644 index 9247e879f..000000000 --- a/WallpaperPicker/src/com/android/gallery3d/exif/ExifInterface.java +++ /dev/null @@ -1,2407 +0,0 @@ -/* - * Copyright (C) 2013 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 com.android.gallery3d.exif; - -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.util.SparseIntArray; - -import java.io.BufferedInputStream; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.Closeable; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.RandomAccessFile; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.channels.FileChannel.MapMode; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Calendar; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.TimeZone; - -/** - * This class provides methods and constants for reading and writing jpeg file - * metadata. It contains a collection of ExifTags, and a collection of - * definitions for creating valid ExifTags. The collection of ExifTags can be - * updated by: reading new ones from a file, deleting or adding existing ones, - * or building new ExifTags from a tag definition. These ExifTags can be written - * to a valid jpeg image as exif metadata. - * <p> - * Each ExifTag has a tag ID (TID) and is stored in a specific image file - * directory (IFD) as specified by the exif standard. A tag definition can be - * looked up with a constant that is a combination of TID and IFD. This - * definition has information about the type, number of components, and valid - * IFDs for a tag. - * - * @see ExifTag - */ -public class ExifInterface { - public static final int TAG_NULL = -1; - public static final int IFD_NULL = -1; - public static final int DEFINITION_NULL = 0; - - /** - * Tag constants for Jeita EXIF 2.2 - */ - - // IFD 0 - public static final int TAG_IMAGE_WIDTH = - defineTag(IfdId.TYPE_IFD_0, (short) 0x0100); - public static final int TAG_IMAGE_LENGTH = - defineTag(IfdId.TYPE_IFD_0, (short) 0x0101); // Image height - public static final int TAG_BITS_PER_SAMPLE = - defineTag(IfdId.TYPE_IFD_0, (short) 0x0102); - public static final int TAG_COMPRESSION = - defineTag(IfdId.TYPE_IFD_0, (short) 0x0103); - public static final int TAG_PHOTOMETRIC_INTERPRETATION = - defineTag(IfdId.TYPE_IFD_0, (short) 0x0106); - public static final int TAG_IMAGE_DESCRIPTION = - defineTag(IfdId.TYPE_IFD_0, (short) 0x010E); - public static final int TAG_MAKE = - defineTag(IfdId.TYPE_IFD_0, (short) 0x010F); - public static final int TAG_MODEL = - defineTag(IfdId.TYPE_IFD_0, (short) 0x0110); - public static final int TAG_STRIP_OFFSETS = - defineTag(IfdId.TYPE_IFD_0, (short) 0x0111); - public static final int TAG_ORIENTATION = - defineTag(IfdId.TYPE_IFD_0, (short) 0x0112); - public static final int TAG_SAMPLES_PER_PIXEL = - defineTag(IfdId.TYPE_IFD_0, (short) 0x0115); - public static final int TAG_ROWS_PER_STRIP = - defineTag(IfdId.TYPE_IFD_0, (short) 0x0116); - public static final int TAG_STRIP_BYTE_COUNTS = - defineTag(IfdId.TYPE_IFD_0, (short) 0x0117); - public static final int TAG_X_RESOLUTION = - defineTag(IfdId.TYPE_IFD_0, (short) 0x011A); - public static final int TAG_Y_RESOLUTION = - defineTag(IfdId.TYPE_IFD_0, (short) 0x011B); - public static final int TAG_PLANAR_CONFIGURATION = - defineTag(IfdId.TYPE_IFD_0, (short) 0x011C); - public static final int TAG_RESOLUTION_UNIT = - defineTag(IfdId.TYPE_IFD_0, (short) 0x0128); - public static final int TAG_TRANSFER_FUNCTION = - defineTag(IfdId.TYPE_IFD_0, (short) 0x012D); - public static final int TAG_SOFTWARE = - defineTag(IfdId.TYPE_IFD_0, (short) 0x0131); - public static final int TAG_DATE_TIME = - defineTag(IfdId.TYPE_IFD_0, (short) 0x0132); - public static final int TAG_ARTIST = - defineTag(IfdId.TYPE_IFD_0, (short) 0x013B); - public static final int TAG_WHITE_POINT = - defineTag(IfdId.TYPE_IFD_0, (short) 0x013E); - public static final int TAG_PRIMARY_CHROMATICITIES = - defineTag(IfdId.TYPE_IFD_0, (short) 0x013F); - public static final int TAG_Y_CB_CR_COEFFICIENTS = - defineTag(IfdId.TYPE_IFD_0, (short) 0x0211); - public static final int TAG_Y_CB_CR_SUB_SAMPLING = - defineTag(IfdId.TYPE_IFD_0, (short) 0x0212); - public static final int TAG_Y_CB_CR_POSITIONING = - defineTag(IfdId.TYPE_IFD_0, (short) 0x0213); - public static final int TAG_REFERENCE_BLACK_WHITE = - defineTag(IfdId.TYPE_IFD_0, (short) 0x0214); - public static final int TAG_COPYRIGHT = - defineTag(IfdId.TYPE_IFD_0, (short) 0x8298); - public static final int TAG_EXIF_IFD = - defineTag(IfdId.TYPE_IFD_0, (short) 0x8769); - public static final int TAG_GPS_IFD = - defineTag(IfdId.TYPE_IFD_0, (short) 0x8825); - // IFD 1 - public static final int TAG_JPEG_INTERCHANGE_FORMAT = - defineTag(IfdId.TYPE_IFD_1, (short) 0x0201); - public static final int TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = - defineTag(IfdId.TYPE_IFD_1, (short) 0x0202); - // IFD Exif Tags - public static final int TAG_EXPOSURE_TIME = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x829A); - public static final int TAG_F_NUMBER = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x829D); - public static final int TAG_EXPOSURE_PROGRAM = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8822); - public static final int TAG_SPECTRAL_SENSITIVITY = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8824); - public static final int TAG_ISO_SPEED_RATINGS = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8827); - public static final int TAG_OECF = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8828); - public static final int TAG_EXIF_VERSION = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9000); - public static final int TAG_DATE_TIME_ORIGINAL = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9003); - public static final int TAG_DATE_TIME_DIGITIZED = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9004); - public static final int TAG_COMPONENTS_CONFIGURATION = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9101); - public static final int TAG_COMPRESSED_BITS_PER_PIXEL = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9102); - public static final int TAG_SHUTTER_SPEED_VALUE = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9201); - public static final int TAG_APERTURE_VALUE = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9202); - public static final int TAG_BRIGHTNESS_VALUE = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9203); - public static final int TAG_EXPOSURE_BIAS_VALUE = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9204); - public static final int TAG_MAX_APERTURE_VALUE = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9205); - public static final int TAG_SUBJECT_DISTANCE = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9206); - public static final int TAG_METERING_MODE = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9207); - public static final int TAG_LIGHT_SOURCE = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9208); - public static final int TAG_FLASH = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9209); - public static final int TAG_FOCAL_LENGTH = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x920A); - public static final int TAG_SUBJECT_AREA = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9214); - public static final int TAG_MAKER_NOTE = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x927C); - public static final int TAG_USER_COMMENT = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9286); - public static final int TAG_SUB_SEC_TIME = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9290); - public static final int TAG_SUB_SEC_TIME_ORIGINAL = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9291); - public static final int TAG_SUB_SEC_TIME_DIGITIZED = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9292); - public static final int TAG_FLASHPIX_VERSION = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA000); - public static final int TAG_COLOR_SPACE = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA001); - public static final int TAG_PIXEL_X_DIMENSION = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA002); - public static final int TAG_PIXEL_Y_DIMENSION = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA003); - public static final int TAG_RELATED_SOUND_FILE = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA004); - public static final int TAG_INTEROPERABILITY_IFD = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA005); - public static final int TAG_FLASH_ENERGY = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20B); - public static final int TAG_SPATIAL_FREQUENCY_RESPONSE = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20C); - public static final int TAG_FOCAL_PLANE_X_RESOLUTION = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20E); - public static final int TAG_FOCAL_PLANE_Y_RESOLUTION = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20F); - public static final int TAG_FOCAL_PLANE_RESOLUTION_UNIT = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA210); - public static final int TAG_SUBJECT_LOCATION = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA214); - public static final int TAG_EXPOSURE_INDEX = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA215); - public static final int TAG_SENSING_METHOD = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA217); - public static final int TAG_FILE_SOURCE = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA300); - public static final int TAG_SCENE_TYPE = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA301); - public static final int TAG_CFA_PATTERN = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA302); - public static final int TAG_CUSTOM_RENDERED = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA401); - public static final int TAG_EXPOSURE_MODE = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA402); - public static final int TAG_WHITE_BALANCE = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA403); - public static final int TAG_DIGITAL_ZOOM_RATIO = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA404); - public static final int TAG_FOCAL_LENGTH_IN_35_MM_FILE = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA405); - public static final int TAG_SCENE_CAPTURE_TYPE = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA406); - public static final int TAG_GAIN_CONTROL = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA407); - public static final int TAG_CONTRAST = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA408); - public static final int TAG_SATURATION = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA409); - public static final int TAG_SHARPNESS = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA40A); - public static final int TAG_DEVICE_SETTING_DESCRIPTION = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA40B); - public static final int TAG_SUBJECT_DISTANCE_RANGE = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA40C); - public static final int TAG_IMAGE_UNIQUE_ID = - defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA420); - // IFD GPS tags - public static final int TAG_GPS_VERSION_ID = - defineTag(IfdId.TYPE_IFD_GPS, (short) 0); - public static final int TAG_GPS_LATITUDE_REF = - defineTag(IfdId.TYPE_IFD_GPS, (short) 1); - public static final int TAG_GPS_LATITUDE = - defineTag(IfdId.TYPE_IFD_GPS, (short) 2); - public static final int TAG_GPS_LONGITUDE_REF = - defineTag(IfdId.TYPE_IFD_GPS, (short) 3); - public static final int TAG_GPS_LONGITUDE = - defineTag(IfdId.TYPE_IFD_GPS, (short) 4); - public static final int TAG_GPS_ALTITUDE_REF = - defineTag(IfdId.TYPE_IFD_GPS, (short) 5); - public static final int TAG_GPS_ALTITUDE = - defineTag(IfdId.TYPE_IFD_GPS, (short) 6); - public static final int TAG_GPS_TIME_STAMP = - defineTag(IfdId.TYPE_IFD_GPS, (short) 7); - public static final int TAG_GPS_SATTELLITES = - defineTag(IfdId.TYPE_IFD_GPS, (short) 8); - public static final int TAG_GPS_STATUS = - defineTag(IfdId.TYPE_IFD_GPS, (short) 9); - public static final int TAG_GPS_MEASURE_MODE = - defineTag(IfdId.TYPE_IFD_GPS, (short) 10); - public static final int TAG_GPS_DOP = - defineTag(IfdId.TYPE_IFD_GPS, (short) 11); - public static final int TAG_GPS_SPEED_REF = - defineTag(IfdId.TYPE_IFD_GPS, (short) 12); - public static final int TAG_GPS_SPEED = - defineTag(IfdId.TYPE_IFD_GPS, (short) 13); - public static final int TAG_GPS_TRACK_REF = - defineTag(IfdId.TYPE_IFD_GPS, (short) 14); - public static final int TAG_GPS_TRACK = - defineTag(IfdId.TYPE_IFD_GPS, (short) 15); - public static final int TAG_GPS_IMG_DIRECTION_REF = - defineTag(IfdId.TYPE_IFD_GPS, (short) 16); - public static final int TAG_GPS_IMG_DIRECTION = - defineTag(IfdId.TYPE_IFD_GPS, (short) 17); - public static final int TAG_GPS_MAP_DATUM = - defineTag(IfdId.TYPE_IFD_GPS, (short) 18); - public static final int TAG_GPS_DEST_LATITUDE_REF = - defineTag(IfdId.TYPE_IFD_GPS, (short) 19); - public static final int TAG_GPS_DEST_LATITUDE = - defineTag(IfdId.TYPE_IFD_GPS, (short) 20); - public static final int TAG_GPS_DEST_LONGITUDE_REF = - defineTag(IfdId.TYPE_IFD_GPS, (short) 21); - public static final int TAG_GPS_DEST_LONGITUDE = - defineTag(IfdId.TYPE_IFD_GPS, (short) 22); - public static final int TAG_GPS_DEST_BEARING_REF = - defineTag(IfdId.TYPE_IFD_GPS, (short) 23); - public static final int TAG_GPS_DEST_BEARING = - defineTag(IfdId.TYPE_IFD_GPS, (short) 24); - public static final int TAG_GPS_DEST_DISTANCE_REF = - defineTag(IfdId.TYPE_IFD_GPS, (short) 25); - public static final int TAG_GPS_DEST_DISTANCE = - defineTag(IfdId.TYPE_IFD_GPS, (short) 26); - public static final int TAG_GPS_PROCESSING_METHOD = - defineTag(IfdId.TYPE_IFD_GPS, (short) 27); - public static final int TAG_GPS_AREA_INFORMATION = - defineTag(IfdId.TYPE_IFD_GPS, (short) 28); - public static final int TAG_GPS_DATE_STAMP = - defineTag(IfdId.TYPE_IFD_GPS, (short) 29); - public static final int TAG_GPS_DIFFERENTIAL = - defineTag(IfdId.TYPE_IFD_GPS, (short) 30); - // IFD Interoperability tags - public static final int TAG_INTEROPERABILITY_INDEX = - defineTag(IfdId.TYPE_IFD_INTEROPERABILITY, (short) 1); - - /** - * Tags that contain offset markers. These are included in the banned - * defines. - */ - private static HashSet<Short> sOffsetTags = new HashSet<Short>(); - static { - sOffsetTags.add(getTrueTagKey(TAG_GPS_IFD)); - sOffsetTags.add(getTrueTagKey(TAG_EXIF_IFD)); - sOffsetTags.add(getTrueTagKey(TAG_JPEG_INTERCHANGE_FORMAT)); - sOffsetTags.add(getTrueTagKey(TAG_INTEROPERABILITY_IFD)); - sOffsetTags.add(getTrueTagKey(TAG_STRIP_OFFSETS)); - } - - /** - * Tags with definitions that cannot be overridden (banned defines). - */ - protected static HashSet<Short> sBannedDefines = new HashSet<Short>(sOffsetTags); - static { - sBannedDefines.add(getTrueTagKey(TAG_NULL)); - sBannedDefines.add(getTrueTagKey(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH)); - sBannedDefines.add(getTrueTagKey(TAG_STRIP_BYTE_COUNTS)); - } - - /** - * Returns the constant representing a tag with a given TID and default IFD. - */ - public static int defineTag(int ifdId, short tagId) { - return (tagId & 0x0000ffff) | (ifdId << 16); - } - - /** - * Returns the TID for a tag constant. - */ - public static short getTrueTagKey(int tag) { - // Truncate - return (short) tag; - } - - /** - * Returns the default IFD for a tag constant. - */ - public static int getTrueIfd(int tag) { - return tag >>> 16; - } - - /** - * Constants for {@link TAG_ORIENTATION}. They can be interpreted as - * follows: - * <ul> - * <li>TOP_LEFT is the normal orientation.</li> - * <li>TOP_RIGHT is a left-right mirror.</li> - * <li>BOTTOM_LEFT is a 180 degree rotation.</li> - * <li>BOTTOM_RIGHT is a top-bottom mirror.</li> - * <li>LEFT_TOP is mirrored about the top-left<->bottom-right axis.</li> - * <li>RIGHT_TOP is a 90 degree clockwise rotation.</li> - * <li>LEFT_BOTTOM is mirrored about the top-right<->bottom-left axis.</li> - * <li>RIGHT_BOTTOM is a 270 degree clockwise rotation.</li> - * </ul> - */ - public static interface Orientation { - public static final short TOP_LEFT = 1; - public static final short TOP_RIGHT = 2; - public static final short BOTTOM_LEFT = 3; - public static final short BOTTOM_RIGHT = 4; - public static final short LEFT_TOP = 5; - public static final short RIGHT_TOP = 6; - public static final short LEFT_BOTTOM = 7; - public static final short RIGHT_BOTTOM = 8; - } - - /** - * Constants for {@link TAG_Y_CB_CR_POSITIONING} - */ - public static interface YCbCrPositioning { - public static final short CENTERED = 1; - public static final short CO_SITED = 2; - } - - /** - * Constants for {@link TAG_COMPRESSION} - */ - public static interface Compression { - public static final short UNCOMPRESSION = 1; - public static final short JPEG = 6; - } - - /** - * Constants for {@link TAG_RESOLUTION_UNIT} - */ - public static interface ResolutionUnit { - public static final short INCHES = 2; - public static final short CENTIMETERS = 3; - } - - /** - * Constants for {@link TAG_PHOTOMETRIC_INTERPRETATION} - */ - public static interface PhotometricInterpretation { - public static final short RGB = 2; - public static final short YCBCR = 6; - } - - /** - * Constants for {@link TAG_PLANAR_CONFIGURATION} - */ - public static interface PlanarConfiguration { - public static final short CHUNKY = 1; - public static final short PLANAR = 2; - } - - /** - * Constants for {@link TAG_EXPOSURE_PROGRAM} - */ - public static interface ExposureProgram { - public static final short NOT_DEFINED = 0; - public static final short MANUAL = 1; - public static final short NORMAL_PROGRAM = 2; - public static final short APERTURE_PRIORITY = 3; - public static final short SHUTTER_PRIORITY = 4; - public static final short CREATIVE_PROGRAM = 5; - public static final short ACTION_PROGRAM = 6; - public static final short PROTRAIT_MODE = 7; - public static final short LANDSCAPE_MODE = 8; - } - - /** - * Constants for {@link TAG_METERING_MODE} - */ - public static interface MeteringMode { - public static final short UNKNOWN = 0; - public static final short AVERAGE = 1; - public static final short CENTER_WEIGHTED_AVERAGE = 2; - public static final short SPOT = 3; - public static final short MULTISPOT = 4; - public static final short PATTERN = 5; - public static final short PARTAIL = 6; - public static final short OTHER = 255; - } - - /** - * Constants for {@link TAG_FLASH} As the definition in Jeita EXIF 2.2 - * standard, we can treat this constant as bitwise flag. - * <p> - * e.g. - * <p> - * short flash = FIRED | RETURN_STROBE_RETURN_LIGHT_DETECTED | - * MODE_AUTO_MODE - */ - public static interface Flash { - // LSB - public static final short DID_NOT_FIRED = 0; - public static final short FIRED = 1; - // 1st~2nd bits - public static final short RETURN_NO_STROBE_RETURN_DETECTION_FUNCTION = 0 << 1; - public static final short RETURN_STROBE_RETURN_LIGHT_NOT_DETECTED = 2 << 1; - public static final short RETURN_STROBE_RETURN_LIGHT_DETECTED = 3 << 1; - // 3rd~4th bits - public static final short MODE_UNKNOWN = 0 << 3; - public static final short MODE_COMPULSORY_FLASH_FIRING = 1 << 3; - public static final short MODE_COMPULSORY_FLASH_SUPPRESSION = 2 << 3; - public static final short MODE_AUTO_MODE = 3 << 3; - // 5th bit - public static final short FUNCTION_PRESENT = 0 << 5; - public static final short FUNCTION_NO_FUNCTION = 1 << 5; - // 6th bit - public static final short RED_EYE_REDUCTION_NO_OR_UNKNOWN = 0 << 6; - public static final short RED_EYE_REDUCTION_SUPPORT = 1 << 6; - } - - /** - * Constants for {@link TAG_COLOR_SPACE} - */ - public static interface ColorSpace { - public static final short SRGB = 1; - public static final short UNCALIBRATED = (short) 0xFFFF; - } - - /** - * Constants for {@link TAG_EXPOSURE_MODE} - */ - public static interface ExposureMode { - public static final short AUTO_EXPOSURE = 0; - public static final short MANUAL_EXPOSURE = 1; - public static final short AUTO_BRACKET = 2; - } - - /** - * Constants for {@link TAG_WHITE_BALANCE} - */ - public static interface WhiteBalance { - public static final short AUTO = 0; - public static final short MANUAL = 1; - } - - /** - * Constants for {@link TAG_SCENE_CAPTURE_TYPE} - */ - public static interface SceneCapture { - public static final short STANDARD = 0; - public static final short LANDSCAPE = 1; - public static final short PROTRAIT = 2; - public static final short NIGHT_SCENE = 3; - } - - /** - * Constants for {@link TAG_COMPONENTS_CONFIGURATION} - */ - public static interface ComponentsConfiguration { - public static final short NOT_EXIST = 0; - public static final short Y = 1; - public static final short CB = 2; - public static final short CR = 3; - public static final short R = 4; - public static final short G = 5; - public static final short B = 6; - } - - /** - * Constants for {@link TAG_LIGHT_SOURCE} - */ - public static interface LightSource { - public static final short UNKNOWN = 0; - public static final short DAYLIGHT = 1; - public static final short FLUORESCENT = 2; - public static final short TUNGSTEN = 3; - public static final short FLASH = 4; - public static final short FINE_WEATHER = 9; - public static final short CLOUDY_WEATHER = 10; - public static final short SHADE = 11; - public static final short DAYLIGHT_FLUORESCENT = 12; - public static final short DAY_WHITE_FLUORESCENT = 13; - public static final short COOL_WHITE_FLUORESCENT = 14; - public static final short WHITE_FLUORESCENT = 15; - public static final short STANDARD_LIGHT_A = 17; - public static final short STANDARD_LIGHT_B = 18; - public static final short STANDARD_LIGHT_C = 19; - public static final short D55 = 20; - public static final short D65 = 21; - public static final short D75 = 22; - public static final short D50 = 23; - public static final short ISO_STUDIO_TUNGSTEN = 24; - public static final short OTHER = 255; - } - - /** - * Constants for {@link TAG_SENSING_METHOD} - */ - public static interface SensingMethod { - public static final short NOT_DEFINED = 1; - public static final short ONE_CHIP_COLOR = 2; - public static final short TWO_CHIP_COLOR = 3; - public static final short THREE_CHIP_COLOR = 4; - public static final short COLOR_SEQUENTIAL_AREA = 5; - public static final short TRILINEAR = 7; - public static final short COLOR_SEQUENTIAL_LINEAR = 8; - } - - /** - * Constants for {@link TAG_FILE_SOURCE} - */ - public static interface FileSource { - public static final short DSC = 3; - } - - /** - * Constants for {@link TAG_SCENE_TYPE} - */ - public static interface SceneType { - public static final short DIRECT_PHOTOGRAPHED = 1; - } - - /** - * Constants for {@link TAG_GAIN_CONTROL} - */ - public static interface GainControl { - public static final short NONE = 0; - public static final short LOW_UP = 1; - public static final short HIGH_UP = 2; - public static final short LOW_DOWN = 3; - public static final short HIGH_DOWN = 4; - } - - /** - * Constants for {@link TAG_CONTRAST} - */ - public static interface Contrast { - public static final short NORMAL = 0; - public static final short SOFT = 1; - public static final short HARD = 2; - } - - /** - * Constants for {@link TAG_SATURATION} - */ - public static interface Saturation { - public static final short NORMAL = 0; - public static final short LOW = 1; - public static final short HIGH = 2; - } - - /** - * Constants for {@link TAG_SHARPNESS} - */ - public static interface Sharpness { - public static final short NORMAL = 0; - public static final short SOFT = 1; - public static final short HARD = 2; - } - - /** - * Constants for {@link TAG_SUBJECT_DISTANCE} - */ - public static interface SubjectDistance { - public static final short UNKNOWN = 0; - public static final short MACRO = 1; - public static final short CLOSE_VIEW = 2; - public static final short DISTANT_VIEW = 3; - } - - /** - * Constants for {@link TAG_GPS_LATITUDE_REF}, - * {@link TAG_GPS_DEST_LATITUDE_REF} - */ - public static interface GpsLatitudeRef { - public static final String NORTH = "N"; - public static final String SOUTH = "S"; - } - - /** - * Constants for {@link TAG_GPS_LONGITUDE_REF}, - * {@link TAG_GPS_DEST_LONGITUDE_REF} - */ - public static interface GpsLongitudeRef { - public static final String EAST = "E"; - public static final String WEST = "W"; - } - - /** - * Constants for {@link TAG_GPS_ALTITUDE_REF} - */ - public static interface GpsAltitudeRef { - public static final short SEA_LEVEL = 0; - public static final short SEA_LEVEL_NEGATIVE = 1; - } - - /** - * Constants for {@link TAG_GPS_STATUS} - */ - public static interface GpsStatus { - public static final String IN_PROGRESS = "A"; - public static final String INTEROPERABILITY = "V"; - } - - /** - * Constants for {@link TAG_GPS_MEASURE_MODE} - */ - public static interface GpsMeasureMode { - public static final String MODE_2_DIMENSIONAL = "2"; - public static final String MODE_3_DIMENSIONAL = "3"; - } - - /** - * Constants for {@link TAG_GPS_SPEED_REF}, - * {@link TAG_GPS_DEST_DISTANCE_REF} - */ - public static interface GpsSpeedRef { - public static final String KILOMETERS = "K"; - public static final String MILES = "M"; - public static final String KNOTS = "N"; - } - - /** - * Constants for {@link TAG_GPS_TRACK_REF}, - * {@link TAG_GPS_IMG_DIRECTION_REF}, {@link TAG_GPS_DEST_BEARING_REF} - */ - public static interface GpsTrackRef { - public static final String TRUE_DIRECTION = "T"; - public static final String MAGNETIC_DIRECTION = "M"; - } - - /** - * Constants for {@link TAG_GPS_DIFFERENTIAL} - */ - public static interface GpsDifferential { - public static final short WITHOUT_DIFFERENTIAL_CORRECTION = 0; - public static final short DIFFERENTIAL_CORRECTION_APPLIED = 1; - } - - private static final String NULL_ARGUMENT_STRING = "Argument is null"; - private ExifData mData = new ExifData(DEFAULT_BYTE_ORDER); - public static final ByteOrder DEFAULT_BYTE_ORDER = ByteOrder.BIG_ENDIAN; - - public ExifInterface() { - mGPSDateStampFormat.setTimeZone(TimeZone.getTimeZone("UTC")); - } - - /** - * Reads the exif tags from a byte array, clearing this ExifInterface - * object's existing exif tags. - * - * @param jpeg a byte array containing a jpeg compressed image. - * @throws IOException - */ - public void readExif(byte[] jpeg) throws IOException { - readExif(new ByteArrayInputStream(jpeg)); - } - - /** - * Reads the exif tags from an InputStream, clearing this ExifInterface - * object's existing exif tags. - * - * @param inStream an InputStream containing a jpeg compressed image. - * @throws IOException - */ - public void readExif(InputStream inStream) throws IOException { - if (inStream == null) { - throw new IllegalArgumentException(NULL_ARGUMENT_STRING); - } - ExifData d = null; - try { - d = new ExifReader(this).read(inStream); - } catch (ExifInvalidFormatException e) { - throw new IOException("Invalid exif format : " + e); - } - mData = d; - } - - /** - * Reads the exif tags from a file, clearing this ExifInterface object's - * existing exif tags. - * - * @param inFileName a string representing the filepath to jpeg file. - * @throws FileNotFoundException - * @throws IOException - */ - public void readExif(String inFileName) throws FileNotFoundException, IOException { - if (inFileName == null) { - throw new IllegalArgumentException(NULL_ARGUMENT_STRING); - } - InputStream is = null; - try { - is = (InputStream) new BufferedInputStream(new FileInputStream(inFileName)); - readExif(is); - } catch (IOException e) { - closeSilently(is); - throw e; - } - is.close(); - } - - /** - * Sets the exif tags, clearing this ExifInterface object's existing exif - * tags. - * - * @param tags a collection of exif tags to set. - */ - public void setExif(Collection<ExifTag> tags) { - clearExif(); - setTags(tags); - } - - /** - * Clears this ExifInterface object's existing exif tags. - */ - public void clearExif() { - mData = new ExifData(DEFAULT_BYTE_ORDER); - } - - /** - * Writes the tags from this ExifInterface object into a jpeg image, - * removing prior exif tags. - * - * @param jpeg a byte array containing a jpeg compressed image. - * @param exifOutStream an OutputStream to which the jpeg image with added - * exif tags will be written. - * @throws IOException - */ - public void writeExif(byte[] jpeg, OutputStream exifOutStream) throws IOException { - if (jpeg == null || exifOutStream == null) { - throw new IllegalArgumentException(NULL_ARGUMENT_STRING); - } - OutputStream s = getExifWriterStream(exifOutStream); - s.write(jpeg, 0, jpeg.length); - s.flush(); - } - - /** - * Writes the tags from this ExifInterface object into a jpeg compressed - * bitmap, removing prior exif tags. - * - * @param bmap a bitmap to compress and write exif into. - * @param exifOutStream the OutputStream to which the jpeg image with added - * exif tags will be written. - * @throws IOException - */ - public void writeExif(Bitmap bmap, OutputStream exifOutStream) throws IOException { - if (bmap == null || exifOutStream == null) { - throw new IllegalArgumentException(NULL_ARGUMENT_STRING); - } - OutputStream s = getExifWriterStream(exifOutStream); - bmap.compress(Bitmap.CompressFormat.JPEG, 90, s); - s.flush(); - } - - /** - * Writes the tags from this ExifInterface object into a jpeg stream, - * removing prior exif tags. - * - * @param jpegStream an InputStream containing a jpeg compressed image. - * @param exifOutStream an OutputStream to which the jpeg image with added - * exif tags will be written. - * @throws IOException - */ - public void writeExif(InputStream jpegStream, OutputStream exifOutStream) throws IOException { - if (jpegStream == null || exifOutStream == null) { - throw new IllegalArgumentException(NULL_ARGUMENT_STRING); - } - OutputStream s = getExifWriterStream(exifOutStream); - doExifStreamIO(jpegStream, s); - s.flush(); - } - - /** - * Writes the tags from this ExifInterface object into a jpeg image, - * removing prior exif tags. - * - * @param jpeg a byte array containing a jpeg compressed image. - * @param exifOutFileName a String containing the filepath to which the jpeg - * image with added exif tags will be written. - * @throws FileNotFoundException - * @throws IOException - */ - public void writeExif(byte[] jpeg, String exifOutFileName) throws FileNotFoundException, - IOException { - if (jpeg == null || exifOutFileName == null) { - throw new IllegalArgumentException(NULL_ARGUMENT_STRING); - } - OutputStream s = null; - try { - s = getExifWriterStream(exifOutFileName); - s.write(jpeg, 0, jpeg.length); - s.flush(); - } catch (IOException e) { - closeSilently(s); - throw e; - } - s.close(); - } - - /** - * Writes the tags from this ExifInterface object into a jpeg compressed - * bitmap, removing prior exif tags. - * - * @param bmap a bitmap to compress and write exif into. - * @param exifOutFileName a String containing the filepath to which the jpeg - * image with added exif tags will be written. - * @throws FileNotFoundException - * @throws IOException - */ - public void writeExif(Bitmap bmap, String exifOutFileName) throws FileNotFoundException, - IOException { - if (bmap == null || exifOutFileName == null) { - throw new IllegalArgumentException(NULL_ARGUMENT_STRING); - } - OutputStream s = null; - try { - s = getExifWriterStream(exifOutFileName); - bmap.compress(Bitmap.CompressFormat.JPEG, 90, s); - s.flush(); - } catch (IOException e) { - closeSilently(s); - throw e; - } - s.close(); - } - - /** - * Writes the tags from this ExifInterface object into a jpeg stream, - * removing prior exif tags. - * - * @param jpegStream an InputStream containing a jpeg compressed image. - * @param exifOutFileName a String containing the filepath to which the jpeg - * image with added exif tags will be written. - * @throws FileNotFoundException - * @throws IOException - */ - public void writeExif(InputStream jpegStream, String exifOutFileName) - throws FileNotFoundException, IOException { - if (jpegStream == null || exifOutFileName == null) { - throw new IllegalArgumentException(NULL_ARGUMENT_STRING); - } - OutputStream s = null; - try { - s = getExifWriterStream(exifOutFileName); - doExifStreamIO(jpegStream, s); - s.flush(); - } catch (IOException e) { - closeSilently(s); - throw e; - } - s.close(); - } - - /** - * Writes the tags from this ExifInterface object into a jpeg file, removing - * prior exif tags. - * - * @param jpegFileName a String containing the filepath for a jpeg file. - * @param exifOutFileName a String containing the filepath to which the jpeg - * image with added exif tags will be written. - * @throws FileNotFoundException - * @throws IOException - */ - public void writeExif(String jpegFileName, String exifOutFileName) - throws FileNotFoundException, IOException { - if (jpegFileName == null || exifOutFileName == null) { - throw new IllegalArgumentException(NULL_ARGUMENT_STRING); - } - InputStream is = null; - try { - is = new FileInputStream(jpegFileName); - writeExif(is, exifOutFileName); - } catch (IOException e) { - closeSilently(is); - throw e; - } - is.close(); - } - - /** - * Wraps an OutputStream object with an ExifOutputStream. Exif tags in this - * ExifInterface object will be added to a jpeg image written to this - * stream, removing prior exif tags. Other methods of this ExifInterface - * object should not be called until the returned OutputStream has been - * closed. - * - * @param outStream an OutputStream to wrap. - * @return an OutputStream that wraps the outStream parameter, and adds exif - * metadata. A jpeg image should be written to this stream. - */ - public OutputStream getExifWriterStream(OutputStream outStream) { - if (outStream == null) { - throw new IllegalArgumentException(NULL_ARGUMENT_STRING); - } - ExifOutputStream eos = new ExifOutputStream(outStream, this); - eos.setExifData(mData); - return eos; - } - - /** - * Returns an OutputStream object that writes to a file. Exif tags in this - * ExifInterface object will be added to a jpeg image written to this - * stream, removing prior exif tags. Other methods of this ExifInterface - * object should not be called until the returned OutputStream has been - * closed. - * - * @param exifOutFileName an String containing a filepath for a jpeg file. - * @return an OutputStream that writes to the exifOutFileName file, and adds - * exif metadata. A jpeg image should be written to this stream. - * @throws FileNotFoundException - */ - public OutputStream getExifWriterStream(String exifOutFileName) throws FileNotFoundException { - if (exifOutFileName == null) { - throw new IllegalArgumentException(NULL_ARGUMENT_STRING); - } - OutputStream out = null; - try { - out = (OutputStream) new FileOutputStream(exifOutFileName); - } catch (FileNotFoundException e) { - closeSilently(out); - throw e; - } - return getExifWriterStream(out); - } - - /** - * Attempts to do an in-place rewrite the exif metadata in a file for the - * given tags. If tags do not exist or do not have the same size as the - * existing exif tags, this method will fail. - * - * @param filename a String containing a filepath for a jpeg file with exif - * tags to rewrite. - * @param tags tags that will be written into the jpeg file over existing - * tags if possible. - * @return true if success, false if could not overwrite. If false, no - * changes are made to the file. - * @throws FileNotFoundException - * @throws IOException - */ - public boolean rewriteExif(String filename, Collection<ExifTag> tags) - throws FileNotFoundException, IOException { - RandomAccessFile file = null; - InputStream is = null; - boolean ret; - try { - File temp = new File(filename); - is = new BufferedInputStream(new FileInputStream(temp)); - - // Parse beginning of APP1 in exif to find size of exif header. - ExifParser parser = null; - try { - parser = ExifParser.parse(is, this); - } catch (ExifInvalidFormatException e) { - throw new IOException("Invalid exif format : ", e); - } - long exifSize = parser.getOffsetToExifEndFromSOF(); - - // Free up resources - is.close(); - is = null; - - // Open file for memory mapping. - file = new RandomAccessFile(temp, "rw"); - long fileLength = file.length(); - if (fileLength < exifSize) { - throw new IOException("Filesize changed during operation"); - } - - // Map only exif header into memory. - ByteBuffer buf = file.getChannel().map(MapMode.READ_WRITE, 0, exifSize); - - // Attempt to overwrite tag values without changing lengths (avoids - // file copy). - ret = rewriteExif(buf, tags); - } catch (IOException e) { - closeSilently(file); - throw e; - } finally { - closeSilently(is); - } - file.close(); - return ret; - } - - /** - * Attempts to do an in-place rewrite the exif metadata in a ByteBuffer for - * the given tags. If tags do not exist or do not have the same size as the - * existing exif tags, this method will fail. - * - * @param buf a ByteBuffer containing a jpeg file with existing exif tags to - * rewrite. - * @param tags tags that will be written into the jpeg ByteBuffer over - * existing tags if possible. - * @return true if success, false if could not overwrite. If false, no - * changes are made to the ByteBuffer. - * @throws IOException - */ - public boolean rewriteExif(ByteBuffer buf, Collection<ExifTag> tags) throws IOException { - ExifModifier mod = null; - try { - mod = new ExifModifier(buf, this); - for (ExifTag t : tags) { - mod.modifyTag(t); - } - return mod.commit(); - } catch (ExifInvalidFormatException e) { - throw new IOException("Invalid exif format : " + e); - } - } - - /** - * Attempts to do an in-place rewrite of the exif metadata. If this fails, - * fall back to overwriting file. This preserves tags that are not being - * rewritten. - * - * @param filename a String containing a filepath for a jpeg file. - * @param tags tags that will be written into the jpeg file over existing - * tags if possible. - * @throws FileNotFoundException - * @throws IOException - * @see #rewriteExif - */ - public void forceRewriteExif(String filename, Collection<ExifTag> tags) - throws FileNotFoundException, - IOException { - // Attempt in-place write - if (!rewriteExif(filename, tags)) { - // Fall back to doing a copy - ExifData tempData = mData; - mData = new ExifData(DEFAULT_BYTE_ORDER); - FileInputStream is = null; - ByteArrayOutputStream bytes = null; - try { - is = new FileInputStream(filename); - bytes = new ByteArrayOutputStream(); - doExifStreamIO(is, bytes); - byte[] imageBytes = bytes.toByteArray(); - readExif(imageBytes); - setTags(tags); - writeExif(imageBytes, filename); - } catch (IOException e) { - closeSilently(is); - throw e; - } finally { - is.close(); - // Prevent clobbering of mData - mData = tempData; - } - } - } - - /** - * Attempts to do an in-place rewrite of the exif metadata using the tags in - * this ExifInterface object. If this fails, fall back to overwriting file. - * This preserves tags that are not being rewritten. - * - * @param filename a String containing a filepath for a jpeg file. - * @throws FileNotFoundException - * @throws IOException - * @see #rewriteExif - */ - public void forceRewriteExif(String filename) throws FileNotFoundException, IOException { - forceRewriteExif(filename, getAllTags()); - } - - /** - * Get the exif tags in this ExifInterface object or null if none exist. - * - * @return a List of {@link ExifTag}s. - */ - public List<ExifTag> getAllTags() { - return mData.getAllTags(); - } - - /** - * Returns a list of ExifTags that share a TID (which can be obtained by - * calling {@link #getTrueTagKey} on a defined tag constant) or null if none - * exist. - * - * @param tagId a TID as defined in the exif standard (or with - * {@link #defineTag}). - * @return a List of {@link ExifTag}s. - */ - public List<ExifTag> getTagsForTagId(short tagId) { - return mData.getAllTagsForTagId(tagId); - } - - /** - * Returns a list of ExifTags that share an IFD (which can be obtained by - * calling {@link #getTrueIFD} on a defined tag constant) or null if none - * exist. - * - * @param ifdId an IFD as defined in the exif standard (or with - * {@link #defineTag}). - * @return a List of {@link ExifTag}s. - */ - public List<ExifTag> getTagsForIfdId(int ifdId) { - return mData.getAllTagsForIfd(ifdId); - } - - /** - * Gets an ExifTag for an IFD other than the tag's default. - * - * @see #getTag - */ - public ExifTag getTag(int tagId, int ifdId) { - if (!ExifTag.isValidIfd(ifdId)) { - return null; - } - return mData.getTag(getTrueTagKey(tagId), ifdId); - } - - /** - * Returns the ExifTag in that tag's default IFD for a defined tag constant - * or null if none exists. - * - * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}. - * @return an {@link ExifTag} or null if none exists. - */ - public ExifTag getTag(int tagId) { - int ifdId = getDefinedTagDefaultIfd(tagId); - return getTag(tagId, ifdId); - } - - /** - * Gets a tag value for an IFD other than the tag's default. - * - * @see #getTagValue - */ - public Object getTagValue(int tagId, int ifdId) { - ExifTag t = getTag(tagId, ifdId); - return (t == null) ? null : t.getValue(); - } - - /** - * Returns the value of the ExifTag in that tag's default IFD for a defined - * tag constant or null if none exists or the value could not be cast into - * the return type. - * - * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}. - * @return the value of the ExifTag or null if none exists. - */ - public Object getTagValue(int tagId) { - int ifdId = getDefinedTagDefaultIfd(tagId); - return getTagValue(tagId, ifdId); - } - - /* - * Getter methods that are similar to getTagValue. Null is returned if the - * tag value cannot be cast into the return type. - */ - - /** - * @see #getTagValue - */ - public String getTagStringValue(int tagId, int ifdId) { - ExifTag t = getTag(tagId, ifdId); - if (t == null) { - return null; - } - return t.getValueAsString(); - } - - /** - * @see #getTagValue - */ - public String getTagStringValue(int tagId) { - int ifdId = getDefinedTagDefaultIfd(tagId); - return getTagStringValue(tagId, ifdId); - } - - /** - * @see #getTagValue - */ - public Long getTagLongValue(int tagId, int ifdId) { - long[] l = getTagLongValues(tagId, ifdId); - if (l == null || l.length <= 0) { - return null; - } - return Long.valueOf(l[0]); - } - - /** - * @see #getTagValue - */ - public Long getTagLongValue(int tagId) { - int ifdId = getDefinedTagDefaultIfd(tagId); - return getTagLongValue(tagId, ifdId); - } - - /** - * @see #getTagValue - */ - public Integer getTagIntValue(int tagId, int ifdId) { - int[] l = getTagIntValues(tagId, ifdId); - if (l == null || l.length <= 0) { - return null; - } - return Integer.valueOf(l[0]); - } - - /** - * @see #getTagValue - */ - public Integer getTagIntValue(int tagId) { - int ifdId = getDefinedTagDefaultIfd(tagId); - return getTagIntValue(tagId, ifdId); - } - - /** - * @see #getTagValue - */ - public Byte getTagByteValue(int tagId, int ifdId) { - byte[] l = getTagByteValues(tagId, ifdId); - if (l == null || l.length <= 0) { - return null; - } - return Byte.valueOf(l[0]); - } - - /** - * @see #getTagValue - */ - public Byte getTagByteValue(int tagId) { - int ifdId = getDefinedTagDefaultIfd(tagId); - return getTagByteValue(tagId, ifdId); - } - - /** - * @see #getTagValue - */ - public Rational getTagRationalValue(int tagId, int ifdId) { - Rational[] l = getTagRationalValues(tagId, ifdId); - if (l == null || l.length == 0) { - return null; - } - return new Rational(l[0]); - } - - /** - * @see #getTagValue - */ - public Rational getTagRationalValue(int tagId) { - int ifdId = getDefinedTagDefaultIfd(tagId); - return getTagRationalValue(tagId, ifdId); - } - - /** - * @see #getTagValue - */ - public long[] getTagLongValues(int tagId, int ifdId) { - ExifTag t = getTag(tagId, ifdId); - if (t == null) { - return null; - } - return t.getValueAsLongs(); - } - - /** - * @see #getTagValue - */ - public long[] getTagLongValues(int tagId) { - int ifdId = getDefinedTagDefaultIfd(tagId); - return getTagLongValues(tagId, ifdId); - } - - /** - * @see #getTagValue - */ - public int[] getTagIntValues(int tagId, int ifdId) { - ExifTag t = getTag(tagId, ifdId); - if (t == null) { - return null; - } - return t.getValueAsInts(); - } - - /** - * @see #getTagValue - */ - public int[] getTagIntValues(int tagId) { - int ifdId = getDefinedTagDefaultIfd(tagId); - return getTagIntValues(tagId, ifdId); - } - - /** - * @see #getTagValue - */ - public byte[] getTagByteValues(int tagId, int ifdId) { - ExifTag t = getTag(tagId, ifdId); - if (t == null) { - return null; - } - return t.getValueAsBytes(); - } - - /** - * @see #getTagValue - */ - public byte[] getTagByteValues(int tagId) { - int ifdId = getDefinedTagDefaultIfd(tagId); - return getTagByteValues(tagId, ifdId); - } - - /** - * @see #getTagValue - */ - public Rational[] getTagRationalValues(int tagId, int ifdId) { - ExifTag t = getTag(tagId, ifdId); - if (t == null) { - return null; - } - return t.getValueAsRationals(); - } - - /** - * @see #getTagValue - */ - public Rational[] getTagRationalValues(int tagId) { - int ifdId = getDefinedTagDefaultIfd(tagId); - return getTagRationalValues(tagId, ifdId); - } - - /** - * Checks whether a tag has a defined number of elements. - * - * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}. - * @return true if the tag has a defined number of elements. - */ - public boolean isTagCountDefined(int tagId) { - int info = getTagInfo().get(tagId); - // No value in info can be zero, as all tags have a non-zero type - if (info == 0) { - return false; - } - return getComponentCountFromInfo(info) != ExifTag.SIZE_UNDEFINED; - } - - /** - * Gets the defined number of elements for a tag. - * - * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}. - * @return the number of elements or {@link ExifTag#SIZE_UNDEFINED} if the - * tag or the number of elements is not defined. - */ - public int getDefinedTagCount(int tagId) { - int info = getTagInfo().get(tagId); - if (info == 0) { - return ExifTag.SIZE_UNDEFINED; - } - return getComponentCountFromInfo(info); - } - - /** - * Gets the number of elements for an ExifTag in a given IFD. - * - * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}. - * @param ifdId the IFD containing the ExifTag to check. - * @return the number of elements in the ExifTag, if the tag's size is - * undefined this will return the actual number of elements that is - * in the ExifTag's value. - */ - public int getActualTagCount(int tagId, int ifdId) { - ExifTag t = getTag(tagId, ifdId); - if (t == null) { - return 0; - } - return t.getComponentCount(); - } - - /** - * Gets the default IFD for a tag. - * - * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}. - * @return the default IFD for a tag definition or {@link #IFD_NULL} if no - * definition exists. - */ - public int getDefinedTagDefaultIfd(int tagId) { - int info = getTagInfo().get(tagId); - if (info == DEFINITION_NULL) { - return IFD_NULL; - } - return getTrueIfd(tagId); - } - - /** - * Gets the defined type for a tag. - * - * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}. - * @return the type. - * @see ExifTag#getDataType() - */ - public short getDefinedTagType(int tagId) { - int info = getTagInfo().get(tagId); - if (info == 0) { - return -1; - } - return getTypeFromInfo(info); - } - - /** - * Returns true if tag TID is one of the following: {@link TAG_EXIF_IFD}, - * {@link TAG_GPS_IFD}, {@link TAG_JPEG_INTERCHANGE_FORMAT}, - * {@link TAG_STRIP_OFFSETS}, {@link TAG_INTEROPERABILITY_IFD} - * <p> - * Note: defining tags with these TID's is disallowed. - * - * @param tag a tag's TID (can be obtained from a defined tag constant with - * {@link #getTrueTagKey}). - * @return true if the TID is that of an offset tag. - */ - protected static boolean isOffsetTag(short tag) { - return sOffsetTags.contains(tag); - } - - /** - * Creates a tag for a defined tag constant in a given IFD if that IFD is - * allowed for the tag. This method will fail anytime the appropriate - * {@link ExifTag#setValue} for this tag's datatype would fail. - * - * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}. - * @param ifdId the IFD that the tag should be in. - * @param val the value of the tag to set. - * @return an ExifTag object or null if one could not be constructed. - * @see #buildTag - */ - public ExifTag buildTag(int tagId, int ifdId, Object val) { - int info = getTagInfo().get(tagId); - if (info == 0 || val == null) { - return null; - } - short type = getTypeFromInfo(info); - int definedCount = getComponentCountFromInfo(info); - boolean hasDefinedCount = (definedCount != ExifTag.SIZE_UNDEFINED); - if (!ExifInterface.isIfdAllowed(info, ifdId)) { - return null; - } - ExifTag t = new ExifTag(getTrueTagKey(tagId), type, definedCount, ifdId, hasDefinedCount); - if (!t.setValue(val)) { - return null; - } - return t; - } - - /** - * Creates a tag for a defined tag constant in the tag's default IFD. - * - * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}. - * @param val the tag's value. - * @return an ExifTag object. - */ - public ExifTag buildTag(int tagId, Object val) { - int ifdId = getTrueIfd(tagId); - return buildTag(tagId, ifdId, val); - } - - protected ExifTag buildUninitializedTag(int tagId) { - int info = getTagInfo().get(tagId); - if (info == 0) { - return null; - } - short type = getTypeFromInfo(info); - int definedCount = getComponentCountFromInfo(info); - boolean hasDefinedCount = (definedCount != ExifTag.SIZE_UNDEFINED); - int ifdId = getTrueIfd(tagId); - ExifTag t = new ExifTag(getTrueTagKey(tagId), type, definedCount, ifdId, hasDefinedCount); - return t; - } - - /** - * Sets the value of an ExifTag if it exists in the given IFD. The value - * must be the correct type and length for that ExifTag. - * - * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}. - * @param ifdId the IFD that the ExifTag is in. - * @param val the value to set. - * @return true if success, false if the ExifTag doesn't exist or the value - * is the wrong type/length. - * @see #setTagValue - */ - public boolean setTagValue(int tagId, int ifdId, Object val) { - ExifTag t = getTag(tagId, ifdId); - if (t == null) { - return false; - } - return t.setValue(val); - } - - /** - * Sets the value of an ExifTag if it exists it's default IFD. The value - * must be the correct type and length for that ExifTag. - * - * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}. - * @param val the value to set. - * @return true if success, false if the ExifTag doesn't exist or the value - * is the wrong type/length. - */ - public boolean setTagValue(int tagId, Object val) { - int ifdId = getDefinedTagDefaultIfd(tagId); - return setTagValue(tagId, ifdId, val); - } - - /** - * Puts an ExifTag into this ExifInterface object's tags, removing a - * previous ExifTag with the same TID and IFD. The IFD it is put into will - * be the one the tag was created with in {@link #buildTag}. - * - * @param tag an ExifTag to put into this ExifInterface's tags. - * @return the previous ExifTag with the same TID and IFD or null if none - * exists. - */ - public ExifTag setTag(ExifTag tag) { - return mData.addTag(tag); - } - - /** - * Puts a collection of ExifTags into this ExifInterface objects's tags. Any - * previous ExifTags with the same TID and IFDs will be removed. - * - * @param tags a Collection of ExifTags. - * @see #setTag - */ - public void setTags(Collection<ExifTag> tags) { - for (ExifTag t : tags) { - setTag(t); - } - } - - /** - * Removes the ExifTag for a tag constant from the given IFD. - * - * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}. - * @param ifdId the IFD of the ExifTag to remove. - */ - public void deleteTag(int tagId, int ifdId) { - mData.removeTag(getTrueTagKey(tagId), ifdId); - } - - /** - * Removes the ExifTag for a tag constant from that tag's default IFD. - * - * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}. - */ - public void deleteTag(int tagId) { - int ifdId = getDefinedTagDefaultIfd(tagId); - deleteTag(tagId, ifdId); - } - - /** - * Creates a new tag definition in this ExifInterface object for a given TID - * and default IFD. Creating a definition with the same TID and default IFD - * as a previous definition will override it. - * - * @param tagId the TID for the tag. - * @param defaultIfd the default IFD for the tag. - * @param tagType the type of the tag (see {@link ExifTag#getDataType()}). - * @param defaultComponentCount the number of elements of this tag's type in - * the tags value. - * @param allowedIfds the IFD's this tag is allowed to be put in. - * @return the defined tag constant (e.g. {@link #TAG_IMAGE_WIDTH}) or - * {@link #TAG_NULL} if the definition could not be made. - */ - public int setTagDefinition(short tagId, int defaultIfd, short tagType, - short defaultComponentCount, int[] allowedIfds) { - if (sBannedDefines.contains(tagId)) { - return TAG_NULL; - } - if (ExifTag.isValidType(tagType) && ExifTag.isValidIfd(defaultIfd)) { - int tagDef = defineTag(defaultIfd, tagId); - if (tagDef == TAG_NULL) { - return TAG_NULL; - } - int[] otherDefs = getTagDefinitionsForTagId(tagId); - SparseIntArray infos = getTagInfo(); - // Make sure defaultIfd is in allowedIfds - boolean defaultCheck = false; - for (int i : allowedIfds) { - if (defaultIfd == i) { - defaultCheck = true; - } - if (!ExifTag.isValidIfd(i)) { - return TAG_NULL; - } - } - if (!defaultCheck) { - return TAG_NULL; - } - - int ifdFlags = getFlagsFromAllowedIfds(allowedIfds); - // Make sure no identical tags can exist in allowedIfds - if (otherDefs != null) { - for (int def : otherDefs) { - int tagInfo = infos.get(def); - int allowedFlags = getAllowedIfdFlagsFromInfo(tagInfo); - if ((ifdFlags & allowedFlags) != 0) { - return TAG_NULL; - } - } - } - getTagInfo().put(tagDef, ifdFlags << 24 | (tagType << 16) | defaultComponentCount); - return tagDef; - } - return TAG_NULL; - } - - protected int getTagDefinition(short tagId, int defaultIfd) { - return getTagInfo().get(defineTag(defaultIfd, tagId)); - } - - protected int[] getTagDefinitionsForTagId(short tagId) { - int[] ifds = IfdData.getIfds(); - int[] defs = new int[ifds.length]; - int counter = 0; - SparseIntArray infos = getTagInfo(); - for (int i : ifds) { - int def = defineTag(i, tagId); - if (infos.get(def) != DEFINITION_NULL) { - defs[counter++] = def; - } - } - if (counter == 0) { - return null; - } - - return Arrays.copyOfRange(defs, 0, counter); - } - - protected int getTagDefinitionForTag(ExifTag tag) { - short type = tag.getDataType(); - int count = tag.getComponentCount(); - int ifd = tag.getIfd(); - return getTagDefinitionForTag(tag.getTagId(), type, count, ifd); - } - - protected int getTagDefinitionForTag(short tagId, short type, int count, int ifd) { - int[] defs = getTagDefinitionsForTagId(tagId); - if (defs == null) { - return TAG_NULL; - } - SparseIntArray infos = getTagInfo(); - int ret = TAG_NULL; - for (int i : defs) { - int info = infos.get(i); - short def_type = getTypeFromInfo(info); - int def_count = getComponentCountFromInfo(info); - int[] def_ifds = getAllowedIfdsFromInfo(info); - boolean valid_ifd = false; - for (int j : def_ifds) { - if (j == ifd) { - valid_ifd = true; - break; - } - } - if (valid_ifd && type == def_type - && (count == def_count || def_count == ExifTag.SIZE_UNDEFINED)) { - ret = i; - break; - } - } - return ret; - } - - /** - * Removes a tag definition for given defined tag constant. - * - * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}. - */ - public void removeTagDefinition(int tagId) { - getTagInfo().delete(tagId); - } - - /** - * Resets tag definitions to the default ones. - */ - public void resetTagDefinitions() { - mTagInfo = null; - } - - /** - * Returns the thumbnail from IFD1 as a bitmap, or null if none exists. - * - * @return the thumbnail as a bitmap. - */ - public Bitmap getThumbnailBitmap() { - if (mData.hasCompressedThumbnail()) { - byte[] thumb = mData.getCompressedThumbnail(); - return BitmapFactory.decodeByteArray(thumb, 0, thumb.length); - } else if (mData.hasUncompressedStrip()) { - // TODO: implement uncompressed - } - return null; - } - - /** - * Returns the thumbnail from IFD1 as a byte array, or null if none exists. - * The bytes may either be an uncompressed strip as specified in the exif - * standard or a jpeg compressed image. - * - * @return the thumbnail as a byte array. - */ - public byte[] getThumbnailBytes() { - if (mData.hasCompressedThumbnail()) { - return mData.getCompressedThumbnail(); - } else if (mData.hasUncompressedStrip()) { - // TODO: implement this - } - return null; - } - - /** - * Returns the thumbnail if it is jpeg compressed, or null if none exists. - * - * @return the thumbnail as a byte array. - */ - public byte[] getThumbnail() { - return mData.getCompressedThumbnail(); - } - - /** - * Check if thumbnail is compressed. - * - * @return true if the thumbnail is compressed. - */ - public boolean isThumbnailCompressed() { - return mData.hasCompressedThumbnail(); - } - - /** - * Check if thumbnail exists. - * - * @return true if a compressed thumbnail exists. - */ - public boolean hasThumbnail() { - // TODO: add back in uncompressed strip - return mData.hasCompressedThumbnail(); - } - - // TODO: uncompressed thumbnail setters - - /** - * Sets the thumbnail to be a jpeg compressed image. Clears any prior - * thumbnail. - * - * @param thumb a byte array containing a jpeg compressed image. - * @return true if the thumbnail was set. - */ - public boolean setCompressedThumbnail(byte[] thumb) { - mData.clearThumbnailAndStrips(); - mData.setCompressedThumbnail(thumb); - return true; - } - - /** - * Sets the thumbnail to be a jpeg compressed bitmap. Clears any prior - * thumbnail. - * - * @param thumb a bitmap to compress to a jpeg thumbnail. - * @return true if the thumbnail was set. - */ - public boolean setCompressedThumbnail(Bitmap thumb) { - ByteArrayOutputStream thumbnail = new ByteArrayOutputStream(); - if (!thumb.compress(Bitmap.CompressFormat.JPEG, 90, thumbnail)) { - return false; - } - return setCompressedThumbnail(thumbnail.toByteArray()); - } - - /** - * Clears the compressed thumbnail if it exists. - */ - public void removeCompressedThumbnail() { - mData.setCompressedThumbnail(null); - } - - // Convenience methods: - - /** - * Decodes the user comment tag into string as specified in the EXIF - * standard. Returns null if decoding failed. - */ - public String getUserComment() { - return mData.getUserComment(); - } - - /** - * Returns the Orientation ExifTag value for a given number of degrees. - * - * @param degrees the amount an image is rotated in degrees. - */ - public static short getOrientationValueForRotation(int degrees) { - degrees %= 360; - if (degrees < 0) { - degrees += 360; - } - if (degrees < 90) { - return Orientation.TOP_LEFT; // 0 degrees - } else if (degrees < 180) { - return Orientation.RIGHT_TOP; // 90 degrees cw - } else if (degrees < 270) { - return Orientation.BOTTOM_LEFT; // 180 degrees - } else { - return Orientation.RIGHT_BOTTOM; // 270 degrees cw - } - } - - /** - * Returns the rotation degrees corresponding to an ExifTag Orientation - * value. - * - * @param orientation the ExifTag Orientation value. - */ - public static int getRotationForOrientationValue(short orientation) { - switch (orientation) { - case Orientation.TOP_LEFT: - return 0; - case Orientation.RIGHT_TOP: - return 90; - case Orientation.BOTTOM_LEFT: - return 180; - case Orientation.RIGHT_BOTTOM: - return 270; - default: - return 0; - } - } - - /** - * Gets the double representation of the GPS latitude or longitude - * coordinate. - * - * @param coordinate an array of 3 Rationals representing the degrees, - * minutes, and seconds of the GPS location as defined in the - * exif specification. - * @param reference a GPS reference reperesented by a String containing "N", - * "S", "E", or "W". - * @return the GPS coordinate represented as degrees + minutes/60 + - * seconds/3600 - */ - public static double convertLatOrLongToDouble(Rational[] coordinate, String reference) { - try { - double degrees = coordinate[0].toDouble(); - double minutes = coordinate[1].toDouble(); - double seconds = coordinate[2].toDouble(); - double result = degrees + minutes / 60.0 + seconds / 3600.0; - if ((reference.equals("S") || reference.equals("W"))) { - return -result; - } - return result; - } catch (ArrayIndexOutOfBoundsException e) { - throw new IllegalArgumentException(); - } - } - - /** - * Gets the GPS latitude and longitude as a pair of doubles from this - * ExifInterface object's tags, or null if the necessary tags do not exist. - * - * @return an array of 2 doubles containing the latitude, and longitude - * respectively. - * @see #convertLatOrLongToDouble - */ - public double[] getLatLongAsDoubles() { - Rational[] latitude = getTagRationalValues(TAG_GPS_LATITUDE); - String latitudeRef = getTagStringValue(TAG_GPS_LATITUDE_REF); - Rational[] longitude = getTagRationalValues(TAG_GPS_LONGITUDE); - String longitudeRef = getTagStringValue(TAG_GPS_LONGITUDE_REF); - if (latitude == null || longitude == null || latitudeRef == null || longitudeRef == null - || latitude.length < 3 || longitude.length < 3) { - return null; - } - double[] latLon = new double[2]; - latLon[0] = convertLatOrLongToDouble(latitude, latitudeRef); - latLon[1] = convertLatOrLongToDouble(longitude, longitudeRef); - return latLon; - } - - private static final String GPS_DATE_FORMAT_STR = "yyyy:MM:dd"; - private static final String DATETIME_FORMAT_STR = "yyyy:MM:dd kk:mm:ss"; - private final DateFormat mDateTimeStampFormat = new SimpleDateFormat(DATETIME_FORMAT_STR); - private final DateFormat mGPSDateStampFormat = new SimpleDateFormat(GPS_DATE_FORMAT_STR); - private final Calendar mGPSTimeStampCalendar = Calendar - .getInstance(TimeZone.getTimeZone("UTC")); - - /** - * Creates, formats, and sets the DateTimeStamp tag for one of: - * {@link #TAG_DATE_TIME}, {@link #TAG_DATE_TIME_DIGITIZED}, - * {@link #TAG_DATE_TIME_ORIGINAL}. - * - * @param tagId one of the DateTimeStamp tags. - * @param timestamp a timestamp to format. - * @param timezone a TimeZone object. - * @return true if success, false if the tag could not be set. - */ - public boolean addDateTimeStampTag(int tagId, long timestamp, TimeZone timezone) { - if (tagId == TAG_DATE_TIME || tagId == TAG_DATE_TIME_DIGITIZED - || tagId == TAG_DATE_TIME_ORIGINAL) { - mDateTimeStampFormat.setTimeZone(timezone); - ExifTag t = buildTag(tagId, mDateTimeStampFormat.format(timestamp)); - if (t == null) { - return false; - } - setTag(t); - } else { - return false; - } - return true; - } - - /** - * Creates and sets all to the GPS tags for a give latitude and longitude. - * - * @param latitude a GPS latitude coordinate. - * @param longitude a GPS longitude coordinate. - * @return true if success, false if they could not be created or set. - */ - public boolean addGpsTags(double latitude, double longitude) { - ExifTag latTag = buildTag(TAG_GPS_LATITUDE, toExifLatLong(latitude)); - ExifTag longTag = buildTag(TAG_GPS_LONGITUDE, toExifLatLong(longitude)); - ExifTag latRefTag = buildTag(TAG_GPS_LATITUDE_REF, - latitude >= 0 ? ExifInterface.GpsLatitudeRef.NORTH - : ExifInterface.GpsLatitudeRef.SOUTH); - ExifTag longRefTag = buildTag(TAG_GPS_LONGITUDE_REF, - longitude >= 0 ? ExifInterface.GpsLongitudeRef.EAST - : ExifInterface.GpsLongitudeRef.WEST); - if (latTag == null || longTag == null || latRefTag == null || longRefTag == null) { - return false; - } - setTag(latTag); - setTag(longTag); - setTag(latRefTag); - setTag(longRefTag); - return true; - } - - /** - * Creates and sets the GPS timestamp tag. - * - * @param timestamp a GPS timestamp. - * @return true if success, false if could not be created or set. - */ - public boolean addGpsDateTimeStampTag(long timestamp) { - ExifTag t = buildTag(TAG_GPS_DATE_STAMP, mGPSDateStampFormat.format(timestamp)); - if (t == null) { - return false; - } - setTag(t); - mGPSTimeStampCalendar.setTimeInMillis(timestamp); - t = buildTag(TAG_GPS_TIME_STAMP, new Rational[] { - new Rational(mGPSTimeStampCalendar.get(Calendar.HOUR_OF_DAY), 1), - new Rational(mGPSTimeStampCalendar.get(Calendar.MINUTE), 1), - new Rational(mGPSTimeStampCalendar.get(Calendar.SECOND), 1) - }); - if (t == null) { - return false; - } - setTag(t); - return true; - } - - private static Rational[] 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 Rational[] { - new Rational(degrees, 1), new Rational(minutes, 1), new Rational(seconds, 100) - }; - } - - private void doExifStreamIO(InputStream is, OutputStream os) throws IOException { - byte[] buf = new byte[1024]; - int ret = is.read(buf, 0, 1024); - while (ret != -1) { - os.write(buf, 0, ret); - ret = is.read(buf, 0, 1024); - } - } - - protected static void closeSilently(Closeable c) { - if (c != null) { - try { - c.close(); - } catch (Throwable e) { - // ignored - } - } - } - - private SparseIntArray mTagInfo = null; - - protected SparseIntArray getTagInfo() { - if (mTagInfo == null) { - mTagInfo = new SparseIntArray(); - initTagInfo(); - } - return mTagInfo; - } - - private void initTagInfo() { - /** - * We put tag information in a 4-bytes integer. The first byte a bitmask - * representing the allowed IFDs of the tag, the second byte is the data - * type, and the last two byte are a short value indicating the default - * component count of this tag. - */ - // IFD0 tags - int[] ifdAllowedIfds = { - IfdId.TYPE_IFD_0, IfdId.TYPE_IFD_1 - }; - int ifdFlags = getFlagsFromAllowedIfds(ifdAllowedIfds) << 24; - mTagInfo.put(ExifInterface.TAG_MAKE, - ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_IMAGE_WIDTH, - ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1); - mTagInfo.put(ExifInterface.TAG_IMAGE_LENGTH, - ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1); - mTagInfo.put(ExifInterface.TAG_BITS_PER_SAMPLE, - ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 3); - mTagInfo.put(ExifInterface.TAG_COMPRESSION, - ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); - mTagInfo.put(ExifInterface.TAG_PHOTOMETRIC_INTERPRETATION, - ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); - mTagInfo.put(ExifInterface.TAG_ORIENTATION, ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 - | 1); - mTagInfo.put(ExifInterface.TAG_SAMPLES_PER_PIXEL, - ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); - mTagInfo.put(ExifInterface.TAG_PLANAR_CONFIGURATION, - ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); - mTagInfo.put(ExifInterface.TAG_Y_CB_CR_SUB_SAMPLING, - ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 2); - mTagInfo.put(ExifInterface.TAG_Y_CB_CR_POSITIONING, - ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); - mTagInfo.put(ExifInterface.TAG_X_RESOLUTION, - ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_Y_RESOLUTION, - ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_RESOLUTION_UNIT, - ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); - mTagInfo.put(ExifInterface.TAG_STRIP_OFFSETS, - ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_ROWS_PER_STRIP, - ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1); - mTagInfo.put(ExifInterface.TAG_STRIP_BYTE_COUNTS, - ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_TRANSFER_FUNCTION, - ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 3 * 256); - mTagInfo.put(ExifInterface.TAG_WHITE_POINT, - ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 2); - mTagInfo.put(ExifInterface.TAG_PRIMARY_CHROMATICITIES, - ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 6); - mTagInfo.put(ExifInterface.TAG_Y_CB_CR_COEFFICIENTS, - ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 3); - mTagInfo.put(ExifInterface.TAG_REFERENCE_BLACK_WHITE, - ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 6); - mTagInfo.put(ExifInterface.TAG_DATE_TIME, - ifdFlags | ExifTag.TYPE_ASCII << 16 | 20); - mTagInfo.put(ExifInterface.TAG_IMAGE_DESCRIPTION, - ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_MAKE, - ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_MODEL, - ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_SOFTWARE, - ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_ARTIST, - ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_COPYRIGHT, - ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_EXIF_IFD, - ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1); - mTagInfo.put(ExifInterface.TAG_GPS_IFD, - ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1); - // IFD1 tags - int[] ifd1AllowedIfds = { - IfdId.TYPE_IFD_1 - }; - int ifdFlags1 = getFlagsFromAllowedIfds(ifd1AllowedIfds) << 24; - mTagInfo.put(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT, - ifdFlags1 | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1); - mTagInfo.put(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, - ifdFlags1 | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1); - // Exif tags - int[] exifAllowedIfds = { - IfdId.TYPE_IFD_EXIF - }; - int exifFlags = getFlagsFromAllowedIfds(exifAllowedIfds) << 24; - mTagInfo.put(ExifInterface.TAG_EXIF_VERSION, - exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 4); - mTagInfo.put(ExifInterface.TAG_FLASHPIX_VERSION, - exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 4); - mTagInfo.put(ExifInterface.TAG_COLOR_SPACE, - exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); - mTagInfo.put(ExifInterface.TAG_COMPONENTS_CONFIGURATION, - exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 4); - mTagInfo.put(ExifInterface.TAG_COMPRESSED_BITS_PER_PIXEL, - exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_PIXEL_X_DIMENSION, - exifFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1); - mTagInfo.put(ExifInterface.TAG_PIXEL_Y_DIMENSION, - exifFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1); - mTagInfo.put(ExifInterface.TAG_MAKER_NOTE, - exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_USER_COMMENT, - exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_RELATED_SOUND_FILE, - exifFlags | ExifTag.TYPE_ASCII << 16 | 13); - mTagInfo.put(ExifInterface.TAG_DATE_TIME_ORIGINAL, - exifFlags | ExifTag.TYPE_ASCII << 16 | 20); - mTagInfo.put(ExifInterface.TAG_DATE_TIME_DIGITIZED, - exifFlags | ExifTag.TYPE_ASCII << 16 | 20); - mTagInfo.put(ExifInterface.TAG_SUB_SEC_TIME, - exifFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_SUB_SEC_TIME_ORIGINAL, - exifFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_SUB_SEC_TIME_DIGITIZED, - exifFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_IMAGE_UNIQUE_ID, - exifFlags | ExifTag.TYPE_ASCII << 16 | 33); - mTagInfo.put(ExifInterface.TAG_EXPOSURE_TIME, - exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_F_NUMBER, - exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_EXPOSURE_PROGRAM, - exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); - mTagInfo.put(ExifInterface.TAG_SPECTRAL_SENSITIVITY, - exifFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_ISO_SPEED_RATINGS, - exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_OECF, - exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_SHUTTER_SPEED_VALUE, - exifFlags | ExifTag.TYPE_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_APERTURE_VALUE, - exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_BRIGHTNESS_VALUE, - exifFlags | ExifTag.TYPE_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_EXPOSURE_BIAS_VALUE, - exifFlags | ExifTag.TYPE_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_MAX_APERTURE_VALUE, - exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_SUBJECT_DISTANCE, - exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_METERING_MODE, - exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); - mTagInfo.put(ExifInterface.TAG_LIGHT_SOURCE, - exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); - mTagInfo.put(ExifInterface.TAG_FLASH, - exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); - mTagInfo.put(ExifInterface.TAG_FOCAL_LENGTH, - exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_SUBJECT_AREA, - exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_FLASH_ENERGY, - exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_SPATIAL_FREQUENCY_RESPONSE, - exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_FOCAL_PLANE_X_RESOLUTION, - exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_FOCAL_PLANE_Y_RESOLUTION, - exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_FOCAL_PLANE_RESOLUTION_UNIT, - exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); - mTagInfo.put(ExifInterface.TAG_SUBJECT_LOCATION, - exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 2); - mTagInfo.put(ExifInterface.TAG_EXPOSURE_INDEX, - exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_SENSING_METHOD, - exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); - mTagInfo.put(ExifInterface.TAG_FILE_SOURCE, - exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 1); - mTagInfo.put(ExifInterface.TAG_SCENE_TYPE, - exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 1); - mTagInfo.put(ExifInterface.TAG_CFA_PATTERN, - exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_CUSTOM_RENDERED, - exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); - mTagInfo.put(ExifInterface.TAG_EXPOSURE_MODE, - exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); - mTagInfo.put(ExifInterface.TAG_WHITE_BALANCE, - exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); - mTagInfo.put(ExifInterface.TAG_DIGITAL_ZOOM_RATIO, - exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_FOCAL_LENGTH_IN_35_MM_FILE, - exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); - mTagInfo.put(ExifInterface.TAG_SCENE_CAPTURE_TYPE, - exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); - mTagInfo.put(ExifInterface.TAG_GAIN_CONTROL, - exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_CONTRAST, - exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); - mTagInfo.put(ExifInterface.TAG_SATURATION, - exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); - mTagInfo.put(ExifInterface.TAG_SHARPNESS, - exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); - mTagInfo.put(ExifInterface.TAG_DEVICE_SETTING_DESCRIPTION, - exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_SUBJECT_DISTANCE_RANGE, - exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); - mTagInfo.put(ExifInterface.TAG_INTEROPERABILITY_IFD, exifFlags - | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1); - // GPS tag - int[] gpsAllowedIfds = { - IfdId.TYPE_IFD_GPS - }; - int gpsFlags = getFlagsFromAllowedIfds(gpsAllowedIfds) << 24; - mTagInfo.put(ExifInterface.TAG_GPS_VERSION_ID, - gpsFlags | ExifTag.TYPE_UNSIGNED_BYTE << 16 | 4); - mTagInfo.put(ExifInterface.TAG_GPS_LATITUDE_REF, - gpsFlags | ExifTag.TYPE_ASCII << 16 | 2); - mTagInfo.put(ExifInterface.TAG_GPS_LONGITUDE_REF, - gpsFlags | ExifTag.TYPE_ASCII << 16 | 2); - mTagInfo.put(ExifInterface.TAG_GPS_LATITUDE, - gpsFlags | ExifTag.TYPE_RATIONAL << 16 | 3); - mTagInfo.put(ExifInterface.TAG_GPS_LONGITUDE, - gpsFlags | ExifTag.TYPE_RATIONAL << 16 | 3); - mTagInfo.put(ExifInterface.TAG_GPS_ALTITUDE_REF, - gpsFlags | ExifTag.TYPE_UNSIGNED_BYTE << 16 | 1); - mTagInfo.put(ExifInterface.TAG_GPS_ALTITUDE, - gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_GPS_TIME_STAMP, - gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 3); - mTagInfo.put(ExifInterface.TAG_GPS_SATTELLITES, - gpsFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_GPS_STATUS, - gpsFlags | ExifTag.TYPE_ASCII << 16 | 2); - mTagInfo.put(ExifInterface.TAG_GPS_MEASURE_MODE, - gpsFlags | ExifTag.TYPE_ASCII << 16 | 2); - mTagInfo.put(ExifInterface.TAG_GPS_DOP, - gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_GPS_SPEED_REF, - gpsFlags | ExifTag.TYPE_ASCII << 16 | 2); - mTagInfo.put(ExifInterface.TAG_GPS_SPEED, - gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_GPS_TRACK_REF, - gpsFlags | ExifTag.TYPE_ASCII << 16 | 2); - mTagInfo.put(ExifInterface.TAG_GPS_TRACK, - gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_GPS_IMG_DIRECTION_REF, - gpsFlags | ExifTag.TYPE_ASCII << 16 | 2); - mTagInfo.put(ExifInterface.TAG_GPS_IMG_DIRECTION, - gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_GPS_MAP_DATUM, - gpsFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_GPS_DEST_LATITUDE_REF, - gpsFlags | ExifTag.TYPE_ASCII << 16 | 2); - mTagInfo.put(ExifInterface.TAG_GPS_DEST_LATITUDE, - gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_GPS_DEST_BEARING_REF, - gpsFlags | ExifTag.TYPE_ASCII << 16 | 2); - mTagInfo.put(ExifInterface.TAG_GPS_DEST_BEARING, - gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_GPS_DEST_DISTANCE_REF, - gpsFlags | ExifTag.TYPE_ASCII << 16 | 2); - mTagInfo.put(ExifInterface.TAG_GPS_DEST_DISTANCE, - gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); - mTagInfo.put(ExifInterface.TAG_GPS_PROCESSING_METHOD, - gpsFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_GPS_AREA_INFORMATION, - gpsFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED); - mTagInfo.put(ExifInterface.TAG_GPS_DATE_STAMP, - gpsFlags | ExifTag.TYPE_ASCII << 16 | 11); - mTagInfo.put(ExifInterface.TAG_GPS_DIFFERENTIAL, - gpsFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 11); - // Interoperability tag - int[] interopAllowedIfds = { - IfdId.TYPE_IFD_INTEROPERABILITY - }; - int interopFlags = getFlagsFromAllowedIfds(interopAllowedIfds) << 24; - mTagInfo.put(TAG_INTEROPERABILITY_INDEX, interopFlags | ExifTag.TYPE_ASCII << 16 - | ExifTag.SIZE_UNDEFINED); - } - - protected static int getAllowedIfdFlagsFromInfo(int info) { - return info >>> 24; - } - - protected static int[] getAllowedIfdsFromInfo(int info) { - int ifdFlags = getAllowedIfdFlagsFromInfo(info); - int[] ifds = IfdData.getIfds(); - ArrayList<Integer> l = new ArrayList<Integer>(); - for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) { - int flag = (ifdFlags >> i) & 1; - if (flag == 1) { - l.add(ifds[i]); - } - } - if (l.size() <= 0) { - return null; - } - int[] ret = new int[l.size()]; - int j = 0; - for (int i : l) { - ret[j++] = i; - } - return ret; - } - - protected static boolean isIfdAllowed(int info, int ifd) { - int[] ifds = IfdData.getIfds(); - int ifdFlags = getAllowedIfdFlagsFromInfo(info); - for (int i = 0; i < ifds.length; i++) { - if (ifd == ifds[i] && ((ifdFlags >> i) & 1) == 1) { - return true; - } - } - return false; - } - - protected static int getFlagsFromAllowedIfds(int[] allowedIfds) { - if (allowedIfds == null || allowedIfds.length == 0) { - return 0; - } - int flags = 0; - int[] ifds = IfdData.getIfds(); - for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) { - for (int j : allowedIfds) { - if (ifds[i] == j) { - flags |= 1 << i; - break; - } - } - } - return flags; - } - - protected static short getTypeFromInfo(int info) { - return (short) ((info >> 16) & 0x0ff); - } - - protected static int getComponentCountFromInfo(int info) { - return info & 0x0ffff; - } - -} diff --git a/WallpaperPicker/src/com/android/gallery3d/exif/ExifInvalidFormatException.java b/WallpaperPicker/src/com/android/gallery3d/exif/ExifInvalidFormatException.java deleted file mode 100644 index bf923ec26..000000000 --- a/WallpaperPicker/src/com/android/gallery3d/exif/ExifInvalidFormatException.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.gallery3d.exif; - -public class ExifInvalidFormatException extends Exception { - public ExifInvalidFormatException(String meg) { - super(meg); - } -}
\ No newline at end of file diff --git a/WallpaperPicker/src/com/android/gallery3d/exif/ExifModifier.java b/WallpaperPicker/src/com/android/gallery3d/exif/ExifModifier.java deleted file mode 100644 index 0531cbad9..000000000 --- a/WallpaperPicker/src/com/android/gallery3d/exif/ExifModifier.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.gallery3d.exif; - -import android.util.Log; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.ArrayList; -import java.util.List; - -class ExifModifier { - public static final String TAG = "ExifModifier"; - public static final boolean DEBUG = false; - private final ByteBuffer mByteBuffer; - private final ExifData mTagToModified; - private final List<TagOffset> mTagOffsets = new ArrayList<TagOffset>(); - private final ExifInterface mInterface; - private int mOffsetBase; - - private static class TagOffset { - final int mOffset; - final ExifTag mTag; - - TagOffset(ExifTag tag, int offset) { - mTag = tag; - mOffset = offset; - } - } - - protected ExifModifier(ByteBuffer byteBuffer, ExifInterface iRef) throws IOException, - ExifInvalidFormatException { - mByteBuffer = byteBuffer; - mOffsetBase = byteBuffer.position(); - mInterface = iRef; - InputStream is = null; - try { - is = new ByteBufferInputStream(byteBuffer); - // Do not require any IFD; - ExifParser parser = ExifParser.parse(is, mInterface); - mTagToModified = new ExifData(parser.getByteOrder()); - mOffsetBase += parser.getTiffStartPosition(); - mByteBuffer.position(0); - } finally { - ExifInterface.closeSilently(is); - } - } - - protected ByteOrder getByteOrder() { - return mTagToModified.getByteOrder(); - } - - protected boolean commit() throws IOException, ExifInvalidFormatException { - InputStream is = null; - try { - is = new ByteBufferInputStream(mByteBuffer); - int flag = 0; - IfdData[] ifdDatas = new IfdData[] { - mTagToModified.getIfdData(IfdId.TYPE_IFD_0), - mTagToModified.getIfdData(IfdId.TYPE_IFD_1), - mTagToModified.getIfdData(IfdId.TYPE_IFD_EXIF), - mTagToModified.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY), - mTagToModified.getIfdData(IfdId.TYPE_IFD_GPS) - }; - - if (ifdDatas[IfdId.TYPE_IFD_0] != null) { - flag |= ExifParser.OPTION_IFD_0; - } - if (ifdDatas[IfdId.TYPE_IFD_1] != null) { - flag |= ExifParser.OPTION_IFD_1; - } - if (ifdDatas[IfdId.TYPE_IFD_EXIF] != null) { - flag |= ExifParser.OPTION_IFD_EXIF; - } - if (ifdDatas[IfdId.TYPE_IFD_GPS] != null) { - flag |= ExifParser.OPTION_IFD_GPS; - } - if (ifdDatas[IfdId.TYPE_IFD_INTEROPERABILITY] != null) { - flag |= ExifParser.OPTION_IFD_INTEROPERABILITY; - } - - ExifParser parser = ExifParser.parse(is, flag, mInterface); - int event = parser.next(); - IfdData currIfd = null; - while (event != ExifParser.EVENT_END) { - switch (event) { - case ExifParser.EVENT_START_OF_IFD: - currIfd = ifdDatas[parser.getCurrentIfd()]; - if (currIfd == null) { - parser.skipRemainingTagsInCurrentIfd(); - } - break; - case ExifParser.EVENT_NEW_TAG: - ExifTag oldTag = parser.getTag(); - ExifTag newTag = currIfd.getTag(oldTag.getTagId()); - if (newTag != null) { - if (newTag.getComponentCount() != oldTag.getComponentCount() - || newTag.getDataType() != oldTag.getDataType()) { - return false; - } else { - mTagOffsets.add(new TagOffset(newTag, oldTag.getOffset())); - currIfd.removeTag(oldTag.getTagId()); - if (currIfd.getTagCount() == 0) { - parser.skipRemainingTagsInCurrentIfd(); - } - } - } - break; - } - event = parser.next(); - } - for (IfdData ifd : ifdDatas) { - if (ifd != null && ifd.getTagCount() > 0) { - return false; - } - } - modify(); - } finally { - ExifInterface.closeSilently(is); - } - return true; - } - - private void modify() { - mByteBuffer.order(getByteOrder()); - for (TagOffset tagOffset : mTagOffsets) { - writeTagValue(tagOffset.mTag, tagOffset.mOffset); - } - } - - private void writeTagValue(ExifTag tag, int offset) { - if (DEBUG) { - Log.v(TAG, "modifying tag to: \n" + tag.toString()); - Log.v(TAG, "at offset: " + offset); - } - mByteBuffer.position(offset + mOffsetBase); - switch (tag.getDataType()) { - case ExifTag.TYPE_ASCII: - byte buf[] = tag.getStringByte(); - if (buf.length == tag.getComponentCount()) { - buf[buf.length - 1] = 0; - mByteBuffer.put(buf); - } else { - mByteBuffer.put(buf); - mByteBuffer.put((byte) 0); - } - break; - case ExifTag.TYPE_LONG: - case ExifTag.TYPE_UNSIGNED_LONG: - for (int i = 0, n = tag.getComponentCount(); i < n; i++) { - mByteBuffer.putInt((int) tag.getValueAt(i)); - } - break; - case ExifTag.TYPE_RATIONAL: - case ExifTag.TYPE_UNSIGNED_RATIONAL: - for (int i = 0, n = tag.getComponentCount(); i < n; i++) { - Rational v = tag.getRational(i); - mByteBuffer.putInt((int) v.getNumerator()); - mByteBuffer.putInt((int) v.getDenominator()); - } - break; - case ExifTag.TYPE_UNDEFINED: - case ExifTag.TYPE_UNSIGNED_BYTE: - buf = new byte[tag.getComponentCount()]; - tag.getBytes(buf); - mByteBuffer.put(buf); - break; - case ExifTag.TYPE_UNSIGNED_SHORT: - for (int i = 0, n = tag.getComponentCount(); i < n; i++) { - mByteBuffer.putShort((short) tag.getValueAt(i)); - } - break; - } - } - - public void modifyTag(ExifTag tag) { - mTagToModified.addTag(tag); - } -} diff --git a/WallpaperPicker/src/com/android/gallery3d/exif/ExifOutputStream.java b/WallpaperPicker/src/com/android/gallery3d/exif/ExifOutputStream.java deleted file mode 100644 index 7ca05f2e0..000000000 --- a/WallpaperPicker/src/com/android/gallery3d/exif/ExifOutputStream.java +++ /dev/null @@ -1,518 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.gallery3d.exif; - -import android.util.Log; - -import java.io.BufferedOutputStream; -import java.io.FilterOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.ArrayList; - -/** - * This class provides a way to replace the Exif header of a JPEG image. - * <p> - * Below is an example of writing EXIF data into a file - * - * <pre> - * public static void writeExif(byte[] jpeg, ExifData exif, String path) { - * OutputStream os = null; - * try { - * os = new FileOutputStream(path); - * ExifOutputStream eos = new ExifOutputStream(os); - * // Set the exif header - * eos.setExifData(exif); - * // Write the original jpeg out, the header will be add into the file. - * eos.write(jpeg); - * } catch (FileNotFoundException e) { - * e.printStackTrace(); - * } catch (IOException e) { - * e.printStackTrace(); - * } finally { - * if (os != null) { - * try { - * os.close(); - * } catch (IOException e) { - * e.printStackTrace(); - * } - * } - * } - * } - * </pre> - */ -class ExifOutputStream extends FilterOutputStream { - private static final String TAG = "ExifOutputStream"; - private static final boolean DEBUG = false; - private static final int STREAMBUFFER_SIZE = 0x00010000; // 64Kb - - private static final int STATE_SOI = 0; - private static final int STATE_FRAME_HEADER = 1; - private static final int STATE_JPEG_DATA = 2; - - private static final int EXIF_HEADER = 0x45786966; - private static final short TIFF_HEADER = 0x002A; - private static final short TIFF_BIG_ENDIAN = 0x4d4d; - private static final short TIFF_LITTLE_ENDIAN = 0x4949; - private static final short TAG_SIZE = 12; - private static final short TIFF_HEADER_SIZE = 8; - private static final int MAX_EXIF_SIZE = 65535; - - private ExifData mExifData; - private int mState = STATE_SOI; - private int mByteToSkip; - private int mByteToCopy; - private byte[] mSingleByteArray = new byte[1]; - private ByteBuffer mBuffer = ByteBuffer.allocate(4); - private final ExifInterface mInterface; - - protected ExifOutputStream(OutputStream ou, ExifInterface iRef) { - super(new BufferedOutputStream(ou, STREAMBUFFER_SIZE)); - mInterface = iRef; - } - - /** - * Sets the ExifData to be written into the JPEG file. Should be called - * before writing image data. - */ - protected void setExifData(ExifData exifData) { - mExifData = exifData; - } - - /** - * Gets the Exif header to be written into the JPEF file. - */ - protected ExifData getExifData() { - return mExifData; - } - - private int requestByteToBuffer(int requestByteCount, byte[] buffer - , int offset, int length) { - int byteNeeded = requestByteCount - mBuffer.position(); - int byteToRead = length > byteNeeded ? byteNeeded : length; - mBuffer.put(buffer, offset, byteToRead); - return byteToRead; - } - - /** - * Writes the image out. The input data should be a valid JPEG format. After - * writing, it's Exif header will be replaced by the given header. - */ - @Override - public void write(byte[] buffer, int offset, int length) throws IOException { - while ((mByteToSkip > 0 || mByteToCopy > 0 || mState != STATE_JPEG_DATA) - && length > 0) { - if (mByteToSkip > 0) { - int byteToProcess = length > mByteToSkip ? mByteToSkip : length; - length -= byteToProcess; - mByteToSkip -= byteToProcess; - offset += byteToProcess; - } - if (mByteToCopy > 0) { - int byteToProcess = length > mByteToCopy ? mByteToCopy : length; - out.write(buffer, offset, byteToProcess); - length -= byteToProcess; - mByteToCopy -= byteToProcess; - offset += byteToProcess; - } - if (length == 0) { - return; - } - switch (mState) { - case STATE_SOI: - int byteRead = requestByteToBuffer(2, buffer, offset, length); - offset += byteRead; - length -= byteRead; - if (mBuffer.position() < 2) { - return; - } - mBuffer.rewind(); - if (mBuffer.getShort() != JpegHeader.SOI) { - throw new IOException("Not a valid jpeg image, cannot write exif"); - } - out.write(mBuffer.array(), 0, 2); - mState = STATE_FRAME_HEADER; - mBuffer.rewind(); - writeExifData(); - break; - case STATE_FRAME_HEADER: - // We ignore the APP1 segment and copy all other segments - // until SOF tag. - byteRead = requestByteToBuffer(4, buffer, offset, length); - offset += byteRead; - length -= byteRead; - // Check if this image data doesn't contain SOF. - if (mBuffer.position() == 2) { - short tag = mBuffer.getShort(); - if (tag == JpegHeader.EOI) { - out.write(mBuffer.array(), 0, 2); - mBuffer.rewind(); - } - } - if (mBuffer.position() < 4) { - return; - } - mBuffer.rewind(); - short marker = mBuffer.getShort(); - if (marker == JpegHeader.APP1) { - mByteToSkip = (mBuffer.getShort() & 0x0000ffff) - 2; - mState = STATE_JPEG_DATA; - } else if (!JpegHeader.isSofMarker(marker)) { - out.write(mBuffer.array(), 0, 4); - mByteToCopy = (mBuffer.getShort() & 0x0000ffff) - 2; - } else { - out.write(mBuffer.array(), 0, 4); - mState = STATE_JPEG_DATA; - } - mBuffer.rewind(); - } - } - if (length > 0) { - out.write(buffer, offset, length); - } - } - - /** - * Writes the one bytes out. The input data should be a valid JPEG format. - * After writing, it's Exif header will be replaced by the given header. - */ - @Override - public void write(int oneByte) throws IOException { - mSingleByteArray[0] = (byte) (0xff & oneByte); - write(mSingleByteArray); - } - - /** - * Equivalent to calling write(buffer, 0, buffer.length). - */ - @Override - public void write(byte[] buffer) throws IOException { - write(buffer, 0, buffer.length); - } - - private void writeExifData() throws IOException { - if (mExifData == null) { - return; - } - if (DEBUG) { - Log.v(TAG, "Writing exif data..."); - } - ArrayList<ExifTag> nullTags = stripNullValueTags(mExifData); - createRequiredIfdAndTag(); - int exifSize = calculateAllOffset(); - if (exifSize + 8 > MAX_EXIF_SIZE) { - throw new IOException("Exif header is too large (>64Kb)"); - } - OrderedDataOutputStream dataOutputStream = new OrderedDataOutputStream(out); - dataOutputStream.setByteOrder(ByteOrder.BIG_ENDIAN); - dataOutputStream.writeShort(JpegHeader.APP1); - dataOutputStream.writeShort((short) (exifSize + 8)); - dataOutputStream.writeInt(EXIF_HEADER); - dataOutputStream.writeShort((short) 0x0000); - if (mExifData.getByteOrder() == ByteOrder.BIG_ENDIAN) { - dataOutputStream.writeShort(TIFF_BIG_ENDIAN); - } else { - dataOutputStream.writeShort(TIFF_LITTLE_ENDIAN); - } - dataOutputStream.setByteOrder(mExifData.getByteOrder()); - dataOutputStream.writeShort(TIFF_HEADER); - dataOutputStream.writeInt(8); - writeAllTags(dataOutputStream); - writeThumbnail(dataOutputStream); - for (ExifTag t : nullTags) { - mExifData.addTag(t); - } - } - - private ArrayList<ExifTag> stripNullValueTags(ExifData data) { - ArrayList<ExifTag> nullTags = new ArrayList<ExifTag>(); - for(ExifTag t : data.getAllTags()) { - if (t.getValue() == null && !ExifInterface.isOffsetTag(t.getTagId())) { - data.removeTag(t.getTagId(), t.getIfd()); - nullTags.add(t); - } - } - return nullTags; - } - - private void writeThumbnail(OrderedDataOutputStream dataOutputStream) throws IOException { - if (mExifData.hasCompressedThumbnail()) { - dataOutputStream.write(mExifData.getCompressedThumbnail()); - } else if (mExifData.hasUncompressedStrip()) { - for (int i = 0; i < mExifData.getStripCount(); i++) { - dataOutputStream.write(mExifData.getStrip(i)); - } - } - } - - private void writeAllTags(OrderedDataOutputStream dataOutputStream) throws IOException { - writeIfd(mExifData.getIfdData(IfdId.TYPE_IFD_0), dataOutputStream); - writeIfd(mExifData.getIfdData(IfdId.TYPE_IFD_EXIF), dataOutputStream); - IfdData interoperabilityIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY); - if (interoperabilityIfd != null) { - writeIfd(interoperabilityIfd, dataOutputStream); - } - IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS); - if (gpsIfd != null) { - writeIfd(gpsIfd, dataOutputStream); - } - IfdData ifd1 = mExifData.getIfdData(IfdId.TYPE_IFD_1); - if (ifd1 != null) { - writeIfd(mExifData.getIfdData(IfdId.TYPE_IFD_1), dataOutputStream); - } - } - - private void writeIfd(IfdData ifd, OrderedDataOutputStream dataOutputStream) - throws IOException { - ExifTag[] tags = ifd.getAllTags(); - dataOutputStream.writeShort((short) tags.length); - for (ExifTag tag : tags) { - dataOutputStream.writeShort(tag.getTagId()); - dataOutputStream.writeShort(tag.getDataType()); - dataOutputStream.writeInt(tag.getComponentCount()); - if (DEBUG) { - Log.v(TAG, "\n" + tag.toString()); - } - if (tag.getDataSize() > 4) { - dataOutputStream.writeInt(tag.getOffset()); - } else { - ExifOutputStream.writeTagValue(tag, dataOutputStream); - for (int i = 0, n = 4 - tag.getDataSize(); i < n; i++) { - dataOutputStream.write(0); - } - } - } - dataOutputStream.writeInt(ifd.getOffsetToNextIfd()); - for (ExifTag tag : tags) { - if (tag.getDataSize() > 4) { - ExifOutputStream.writeTagValue(tag, dataOutputStream); - } - } - } - - private int calculateOffsetOfIfd(IfdData ifd, int offset) { - offset += 2 + ifd.getTagCount() * TAG_SIZE + 4; - ExifTag[] tags = ifd.getAllTags(); - for (ExifTag tag : tags) { - if (tag.getDataSize() > 4) { - tag.setOffset(offset); - offset += tag.getDataSize(); - } - } - return offset; - } - - private void createRequiredIfdAndTag() throws IOException { - // IFD0 is required for all file - IfdData ifd0 = mExifData.getIfdData(IfdId.TYPE_IFD_0); - if (ifd0 == null) { - ifd0 = new IfdData(IfdId.TYPE_IFD_0); - mExifData.addIfdData(ifd0); - } - ExifTag exifOffsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_EXIF_IFD); - if (exifOffsetTag == null) { - throw new IOException("No definition for crucial exif tag: " - + ExifInterface.TAG_EXIF_IFD); - } - ifd0.setTag(exifOffsetTag); - - // Exif IFD is required for all files. - IfdData exifIfd = mExifData.getIfdData(IfdId.TYPE_IFD_EXIF); - if (exifIfd == null) { - exifIfd = new IfdData(IfdId.TYPE_IFD_EXIF); - mExifData.addIfdData(exifIfd); - } - - // GPS IFD - IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS); - if (gpsIfd != null) { - ExifTag gpsOffsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_GPS_IFD); - if (gpsOffsetTag == null) { - throw new IOException("No definition for crucial exif tag: " - + ExifInterface.TAG_GPS_IFD); - } - ifd0.setTag(gpsOffsetTag); - } - - // Interoperability IFD - IfdData interIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY); - if (interIfd != null) { - ExifTag interOffsetTag = mInterface - .buildUninitializedTag(ExifInterface.TAG_INTEROPERABILITY_IFD); - if (interOffsetTag == null) { - throw new IOException("No definition for crucial exif tag: " - + ExifInterface.TAG_INTEROPERABILITY_IFD); - } - exifIfd.setTag(interOffsetTag); - } - - IfdData ifd1 = mExifData.getIfdData(IfdId.TYPE_IFD_1); - - // thumbnail - if (mExifData.hasCompressedThumbnail()) { - - if (ifd1 == null) { - ifd1 = new IfdData(IfdId.TYPE_IFD_1); - mExifData.addIfdData(ifd1); - } - - ExifTag offsetTag = mInterface - .buildUninitializedTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT); - if (offsetTag == null) { - throw new IOException("No definition for crucial exif tag: " - + ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT); - } - - ifd1.setTag(offsetTag); - ExifTag lengthTag = mInterface - .buildUninitializedTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH); - if (lengthTag == null) { - throw new IOException("No definition for crucial exif tag: " - + ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH); - } - - lengthTag.setValue(mExifData.getCompressedThumbnail().length); - ifd1.setTag(lengthTag); - - // Get rid of tags for uncompressed if they exist. - ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS)); - ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS)); - } else if (mExifData.hasUncompressedStrip()) { - if (ifd1 == null) { - ifd1 = new IfdData(IfdId.TYPE_IFD_1); - mExifData.addIfdData(ifd1); - } - int stripCount = mExifData.getStripCount(); - ExifTag offsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_STRIP_OFFSETS); - if (offsetTag == null) { - throw new IOException("No definition for crucial exif tag: " - + ExifInterface.TAG_STRIP_OFFSETS); - } - ExifTag lengthTag = mInterface - .buildUninitializedTag(ExifInterface.TAG_STRIP_BYTE_COUNTS); - if (lengthTag == null) { - throw new IOException("No definition for crucial exif tag: " - + ExifInterface.TAG_STRIP_BYTE_COUNTS); - } - long[] lengths = new long[stripCount]; - for (int i = 0; i < mExifData.getStripCount(); i++) { - lengths[i] = mExifData.getStrip(i).length; - } - lengthTag.setValue(lengths); - ifd1.setTag(offsetTag); - ifd1.setTag(lengthTag); - // Get rid of tags for compressed if they exist. - ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT)); - ifd1.removeTag(ExifInterface - .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH)); - } else if (ifd1 != null) { - // Get rid of offset and length tags if there is no thumbnail. - ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS)); - ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS)); - ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT)); - ifd1.removeTag(ExifInterface - .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH)); - } - } - - private int calculateAllOffset() { - int offset = TIFF_HEADER_SIZE; - IfdData ifd0 = mExifData.getIfdData(IfdId.TYPE_IFD_0); - offset = calculateOffsetOfIfd(ifd0, offset); - ifd0.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_EXIF_IFD)).setValue(offset); - - IfdData exifIfd = mExifData.getIfdData(IfdId.TYPE_IFD_EXIF); - offset = calculateOffsetOfIfd(exifIfd, offset); - - IfdData interIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY); - if (interIfd != null) { - exifIfd.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_INTEROPERABILITY_IFD)) - .setValue(offset); - offset = calculateOffsetOfIfd(interIfd, offset); - } - - IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS); - if (gpsIfd != null) { - ifd0.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_GPS_IFD)).setValue(offset); - offset = calculateOffsetOfIfd(gpsIfd, offset); - } - - IfdData ifd1 = mExifData.getIfdData(IfdId.TYPE_IFD_1); - if (ifd1 != null) { - ifd0.setOffsetToNextIfd(offset); - offset = calculateOffsetOfIfd(ifd1, offset); - } - - // thumbnail - if (mExifData.hasCompressedThumbnail()) { - ifd1.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT)) - .setValue(offset); - offset += mExifData.getCompressedThumbnail().length; - } else if (mExifData.hasUncompressedStrip()) { - int stripCount = mExifData.getStripCount(); - long[] offsets = new long[stripCount]; - for (int i = 0; i < mExifData.getStripCount(); i++) { - offsets[i] = offset; - offset += mExifData.getStrip(i).length; - } - ifd1.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS)).setValue( - offsets); - } - return offset; - } - - static void writeTagValue(ExifTag tag, OrderedDataOutputStream dataOutputStream) - throws IOException { - switch (tag.getDataType()) { - case ExifTag.TYPE_ASCII: - byte buf[] = tag.getStringByte(); - if (buf.length == tag.getComponentCount()) { - buf[buf.length - 1] = 0; - dataOutputStream.write(buf); - } else { - dataOutputStream.write(buf); - dataOutputStream.write(0); - } - break; - case ExifTag.TYPE_LONG: - case ExifTag.TYPE_UNSIGNED_LONG: - for (int i = 0, n = tag.getComponentCount(); i < n; i++) { - dataOutputStream.writeInt((int) tag.getValueAt(i)); - } - break; - case ExifTag.TYPE_RATIONAL: - case ExifTag.TYPE_UNSIGNED_RATIONAL: - for (int i = 0, n = tag.getComponentCount(); i < n; i++) { - dataOutputStream.writeRational(tag.getRational(i)); - } - break; - case ExifTag.TYPE_UNDEFINED: - case ExifTag.TYPE_UNSIGNED_BYTE: - buf = new byte[tag.getComponentCount()]; - tag.getBytes(buf); - dataOutputStream.write(buf); - break; - case ExifTag.TYPE_UNSIGNED_SHORT: - for (int i = 0, n = tag.getComponentCount(); i < n; i++) { - dataOutputStream.writeShort((short) tag.getValueAt(i)); - } - break; - } - } -} diff --git a/WallpaperPicker/src/com/android/gallery3d/exif/ExifParser.java b/WallpaperPicker/src/com/android/gallery3d/exif/ExifParser.java deleted file mode 100644 index 5467d423d..000000000 --- a/WallpaperPicker/src/com/android/gallery3d/exif/ExifParser.java +++ /dev/null @@ -1,916 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.gallery3d.exif; - -import android.util.Log; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteOrder; -import java.nio.charset.Charset; -import java.util.Map.Entry; -import java.util.TreeMap; - -/** - * This class provides a low-level EXIF parsing API. Given a JPEG format - * InputStream, the caller can request which IFD's to read via - * {@link #parse(InputStream, int)} with given options. - * <p> - * Below is an example of getting EXIF data from IFD 0 and EXIF IFD using the - * parser. - * - * <pre> - * void parse() { - * ExifParser parser = ExifParser.parse(mImageInputStream, - * ExifParser.OPTION_IFD_0 | ExifParser.OPTIONS_IFD_EXIF); - * int event = parser.next(); - * while (event != ExifParser.EVENT_END) { - * switch (event) { - * case ExifParser.EVENT_START_OF_IFD: - * break; - * case ExifParser.EVENT_NEW_TAG: - * ExifTag tag = parser.getTag(); - * if (!tag.hasValue()) { - * parser.registerForTagValue(tag); - * } else { - * processTag(tag); - * } - * break; - * case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG: - * tag = parser.getTag(); - * if (tag.getDataType() != ExifTag.TYPE_UNDEFINED) { - * processTag(tag); - * } - * break; - * } - * event = parser.next(); - * } - * } - * - * void processTag(ExifTag tag) { - * // process the tag as you like. - * } - * </pre> - */ -class ExifParser { - private static final boolean LOGV = false; - private static final String TAG = "ExifParser"; - /** - * When the parser reaches a new IFD area. Call {@link #getCurrentIfd()} to - * know which IFD we are in. - */ - public static final int EVENT_START_OF_IFD = 0; - /** - * When the parser reaches a new tag. Call {@link #getTag()}to get the - * corresponding tag. - */ - public static final int EVENT_NEW_TAG = 1; - /** - * When the parser reaches the value area of tag that is registered by - * {@link #registerForTagValue(ExifTag)} previously. Call {@link #getTag()} - * to get the corresponding tag. - */ - public static final int EVENT_VALUE_OF_REGISTERED_TAG = 2; - - /** - * When the parser reaches the compressed image area. - */ - public static final int EVENT_COMPRESSED_IMAGE = 3; - /** - * When the parser reaches the uncompressed image strip. Call - * {@link #getStripIndex()} to get the index of the strip. - * - * @see #getStripIndex() - * @see #getStripCount() - */ - public static final int EVENT_UNCOMPRESSED_STRIP = 4; - /** - * When there is nothing more to parse. - */ - public static final int EVENT_END = 5; - - /** - * Option bit to request to parse IFD0. - */ - public static final int OPTION_IFD_0 = 1 << 0; - /** - * Option bit to request to parse IFD1. - */ - public static final int OPTION_IFD_1 = 1 << 1; - /** - * Option bit to request to parse Exif-IFD. - */ - public static final int OPTION_IFD_EXIF = 1 << 2; - /** - * Option bit to request to parse GPS-IFD. - */ - public static final int OPTION_IFD_GPS = 1 << 3; - /** - * Option bit to request to parse Interoperability-IFD. - */ - public static final int OPTION_IFD_INTEROPERABILITY = 1 << 4; - /** - * Option bit to request to parse thumbnail. - */ - public static final int OPTION_THUMBNAIL = 1 << 5; - - protected static final int EXIF_HEADER = 0x45786966; // EXIF header "Exif" - protected static final short EXIF_HEADER_TAIL = (short) 0x0000; // EXIF header in APP1 - - // TIFF header - protected static final short LITTLE_ENDIAN_TAG = (short) 0x4949; // "II" - protected static final short BIG_ENDIAN_TAG = (short) 0x4d4d; // "MM" - protected static final short TIFF_HEADER_TAIL = 0x002A; - - protected static final int TAG_SIZE = 12; - protected static final int OFFSET_SIZE = 2; - - private static final Charset US_ASCII = Charset.forName("US-ASCII"); - - protected static final int DEFAULT_IFD0_OFFSET = 8; - - private final CountedDataInputStream mTiffStream; - private final int mOptions; - private int mIfdStartOffset = 0; - private int mNumOfTagInIfd = 0; - private int mIfdType; - private ExifTag mTag; - private ImageEvent mImageEvent; - private int mStripCount; - private ExifTag mStripSizeTag; - private ExifTag mJpegSizeTag; - private boolean mNeedToParseOffsetsInCurrentIfd; - private boolean mContainExifData = false; - private int mApp1End; - private int mOffsetToApp1EndFromSOF = 0; - private byte[] mDataAboveIfd0; - private int mIfd0Position; - private int mTiffStartPosition; - private final ExifInterface mInterface; - - private static final short TAG_EXIF_IFD = ExifInterface - .getTrueTagKey(ExifInterface.TAG_EXIF_IFD); - private static final short TAG_GPS_IFD = ExifInterface.getTrueTagKey(ExifInterface.TAG_GPS_IFD); - private static final short TAG_INTEROPERABILITY_IFD = ExifInterface - .getTrueTagKey(ExifInterface.TAG_INTEROPERABILITY_IFD); - private static final short TAG_JPEG_INTERCHANGE_FORMAT = ExifInterface - .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT); - private static final short TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = ExifInterface - .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH); - private static final short TAG_STRIP_OFFSETS = ExifInterface - .getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS); - private static final short TAG_STRIP_BYTE_COUNTS = ExifInterface - .getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS); - - private final TreeMap<Integer, Object> mCorrespondingEvent = new TreeMap<Integer, Object>(); - - private boolean isIfdRequested(int ifdType) { - switch (ifdType) { - case IfdId.TYPE_IFD_0: - return (mOptions & OPTION_IFD_0) != 0; - case IfdId.TYPE_IFD_1: - return (mOptions & OPTION_IFD_1) != 0; - case IfdId.TYPE_IFD_EXIF: - return (mOptions & OPTION_IFD_EXIF) != 0; - case IfdId.TYPE_IFD_GPS: - return (mOptions & OPTION_IFD_GPS) != 0; - case IfdId.TYPE_IFD_INTEROPERABILITY: - return (mOptions & OPTION_IFD_INTEROPERABILITY) != 0; - } - return false; - } - - private boolean isThumbnailRequested() { - return (mOptions & OPTION_THUMBNAIL) != 0; - } - - private ExifParser(InputStream inputStream, int options, ExifInterface iRef) - throws IOException, ExifInvalidFormatException { - if (inputStream == null) { - throw new IOException("Null argument inputStream to ExifParser"); - } - if (LOGV) { - Log.v(TAG, "Reading exif..."); - } - mInterface = iRef; - mContainExifData = seekTiffData(inputStream); - mTiffStream = new CountedDataInputStream(inputStream); - mOptions = options; - if (!mContainExifData) { - return; - } - - parseTiffHeader(); - long offset = mTiffStream.readUnsignedInt(); - if (offset > Integer.MAX_VALUE) { - throw new ExifInvalidFormatException("Invalid offset " + offset); - } - mIfd0Position = (int) offset; - mIfdType = IfdId.TYPE_IFD_0; - if (isIfdRequested(IfdId.TYPE_IFD_0) || needToParseOffsetsInCurrentIfd()) { - registerIfd(IfdId.TYPE_IFD_0, offset); - if (offset != DEFAULT_IFD0_OFFSET) { - mDataAboveIfd0 = new byte[(int) offset - DEFAULT_IFD0_OFFSET]; - read(mDataAboveIfd0); - } - } - } - - /** - * Parses the the given InputStream with the given options - * - * @exception IOException - * @exception ExifInvalidFormatException - */ - protected static ExifParser parse(InputStream inputStream, int options, ExifInterface iRef) - throws IOException, ExifInvalidFormatException { - return new ExifParser(inputStream, options, iRef); - } - - /** - * Parses the the given InputStream with default options; that is, every IFD - * and thumbnaill will be parsed. - * - * @exception IOException - * @exception ExifInvalidFormatException - * @see #parse(InputStream, int) - */ - protected static ExifParser parse(InputStream inputStream, ExifInterface iRef) - throws IOException, ExifInvalidFormatException { - return new ExifParser(inputStream, OPTION_IFD_0 | OPTION_IFD_1 - | OPTION_IFD_EXIF | OPTION_IFD_GPS | OPTION_IFD_INTEROPERABILITY - | OPTION_THUMBNAIL, iRef); - } - - /** - * Moves the parser forward and returns the next parsing event - * - * @exception IOException - * @exception ExifInvalidFormatException - * @see #EVENT_START_OF_IFD - * @see #EVENT_NEW_TAG - * @see #EVENT_VALUE_OF_REGISTERED_TAG - * @see #EVENT_COMPRESSED_IMAGE - * @see #EVENT_UNCOMPRESSED_STRIP - * @see #EVENT_END - */ - protected int next() throws IOException, ExifInvalidFormatException { - if (!mContainExifData) { - return EVENT_END; - } - int offset = mTiffStream.getReadByteCount(); - int endOfTags = mIfdStartOffset + OFFSET_SIZE + TAG_SIZE * mNumOfTagInIfd; - if (offset < endOfTags) { - mTag = readTag(); - if (mTag == null) { - return next(); - } - if (mNeedToParseOffsetsInCurrentIfd) { - checkOffsetOrImageTag(mTag); - } - return EVENT_NEW_TAG; - } else if (offset == endOfTags) { - // There is a link to ifd1 at the end of ifd0 - if (mIfdType == IfdId.TYPE_IFD_0) { - long ifdOffset = readUnsignedLong(); - if (isIfdRequested(IfdId.TYPE_IFD_1) || isThumbnailRequested()) { - if (ifdOffset != 0) { - registerIfd(IfdId.TYPE_IFD_1, ifdOffset); - } - } - } else { - int offsetSize = 4; - // Some camera models use invalid length of the offset - if (mCorrespondingEvent.size() > 0) { - offsetSize = mCorrespondingEvent.firstEntry().getKey() - - mTiffStream.getReadByteCount(); - } - if (offsetSize < 4) { - Log.w(TAG, "Invalid size of link to next IFD: " + offsetSize); - } else { - long ifdOffset = readUnsignedLong(); - if (ifdOffset != 0) { - Log.w(TAG, "Invalid link to next IFD: " + ifdOffset); - } - } - } - } - while (mCorrespondingEvent.size() != 0) { - Entry<Integer, Object> entry = mCorrespondingEvent.pollFirstEntry(); - Object event = entry.getValue(); - try { - skipTo(entry.getKey()); - } catch (IOException e) { - Log.w(TAG, "Failed to skip to data at: " + entry.getKey() + - " for " + event.getClass().getName() + ", the file may be broken."); - continue; - } - if (event instanceof IfdEvent) { - mIfdType = ((IfdEvent) event).ifd; - mNumOfTagInIfd = mTiffStream.readUnsignedShort(); - mIfdStartOffset = entry.getKey(); - - if (mNumOfTagInIfd * TAG_SIZE + mIfdStartOffset + OFFSET_SIZE > mApp1End) { - Log.w(TAG, "Invalid size of IFD " + mIfdType); - return EVENT_END; - } - - mNeedToParseOffsetsInCurrentIfd = needToParseOffsetsInCurrentIfd(); - if (((IfdEvent) event).isRequested) { - return EVENT_START_OF_IFD; - } else { - skipRemainingTagsInCurrentIfd(); - } - } else if (event instanceof ImageEvent) { - mImageEvent = (ImageEvent) event; - return mImageEvent.type; - } else { - ExifTagEvent tagEvent = (ExifTagEvent) event; - mTag = tagEvent.tag; - if (mTag.getDataType() != ExifTag.TYPE_UNDEFINED) { - readFullTagValue(mTag); - checkOffsetOrImageTag(mTag); - } - if (tagEvent.isRequested) { - return EVENT_VALUE_OF_REGISTERED_TAG; - } - } - } - return EVENT_END; - } - - /** - * Skips the tags area of current IFD, if the parser is not in the tag area, - * nothing will happen. - * - * @throws IOException - * @throws ExifInvalidFormatException - */ - protected void skipRemainingTagsInCurrentIfd() throws IOException, ExifInvalidFormatException { - int endOfTags = mIfdStartOffset + OFFSET_SIZE + TAG_SIZE * mNumOfTagInIfd; - int offset = mTiffStream.getReadByteCount(); - if (offset > endOfTags) { - return; - } - if (mNeedToParseOffsetsInCurrentIfd) { - while (offset < endOfTags) { - mTag = readTag(); - offset += TAG_SIZE; - if (mTag == null) { - continue; - } - checkOffsetOrImageTag(mTag); - } - } else { - skipTo(endOfTags); - } - long ifdOffset = readUnsignedLong(); - // For ifd0, there is a link to ifd1 in the end of all tags - if (mIfdType == IfdId.TYPE_IFD_0 - && (isIfdRequested(IfdId.TYPE_IFD_1) || isThumbnailRequested())) { - if (ifdOffset > 0) { - registerIfd(IfdId.TYPE_IFD_1, ifdOffset); - } - } - } - - private boolean needToParseOffsetsInCurrentIfd() { - switch (mIfdType) { - case IfdId.TYPE_IFD_0: - return isIfdRequested(IfdId.TYPE_IFD_EXIF) || isIfdRequested(IfdId.TYPE_IFD_GPS) - || isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY) - || isIfdRequested(IfdId.TYPE_IFD_1); - case IfdId.TYPE_IFD_1: - return isThumbnailRequested(); - case IfdId.TYPE_IFD_EXIF: - // The offset to interoperability IFD is located in Exif IFD - return isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY); - default: - return false; - } - } - - /** - * If {@link #next()} return {@link #EVENT_NEW_TAG} or - * {@link #EVENT_VALUE_OF_REGISTERED_TAG}, call this function to get the - * corresponding tag. - * <p> - * For {@link #EVENT_NEW_TAG}, the tag may not contain the value if the size - * of the value is greater than 4 bytes. One should call - * {@link ExifTag#hasValue()} to check if the tag contains value. If there - * is no value,call {@link #registerForTagValue(ExifTag)} to have the parser - * emit {@link #EVENT_VALUE_OF_REGISTERED_TAG} when it reaches the area - * pointed by the offset. - * <p> - * When {@link #EVENT_VALUE_OF_REGISTERED_TAG} is emitted, the value of the - * tag will have already been read except for tags of undefined type. For - * tags of undefined type, call one of the read methods to get the value. - * - * @see #registerForTagValue(ExifTag) - * @see #read(byte[]) - * @see #read(byte[], int, int) - * @see #readLong() - * @see #readRational() - * @see #readString(int) - * @see #readString(int, Charset) - */ - protected ExifTag getTag() { - return mTag; - } - - /** - * Gets number of tags in the current IFD area. - */ - protected int getTagCountInCurrentIfd() { - return mNumOfTagInIfd; - } - - /** - * Gets the ID of current IFD. - * - * @see IfdId#TYPE_IFD_0 - * @see IfdId#TYPE_IFD_1 - * @see IfdId#TYPE_IFD_GPS - * @see IfdId#TYPE_IFD_INTEROPERABILITY - * @see IfdId#TYPE_IFD_EXIF - */ - protected int getCurrentIfd() { - return mIfdType; - } - - /** - * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to - * get the index of this strip. - * - * @see #getStripCount() - */ - protected int getStripIndex() { - return mImageEvent.stripIndex; - } - - /** - * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to - * get the number of strip data. - * - * @see #getStripIndex() - */ - protected int getStripCount() { - return mStripCount; - } - - /** - * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to - * get the strip size. - */ - protected int getStripSize() { - if (mStripSizeTag == null) - return 0; - return (int) mStripSizeTag.getValueAt(0); - } - - /** - * When receiving {@link #EVENT_COMPRESSED_IMAGE}, call this function to get - * the image data size. - */ - protected int getCompressedImageSize() { - if (mJpegSizeTag == null) { - return 0; - } - return (int) mJpegSizeTag.getValueAt(0); - } - - private void skipTo(int offset) throws IOException { - mTiffStream.skipTo(offset); - while (!mCorrespondingEvent.isEmpty() && mCorrespondingEvent.firstKey() < offset) { - mCorrespondingEvent.pollFirstEntry(); - } - } - - /** - * When getting {@link #EVENT_NEW_TAG} in the tag area of IFD, the tag may - * not contain the value if the size of the value is greater than 4 bytes. - * When the value is not available here, call this method so that the parser - * will emit {@link #EVENT_VALUE_OF_REGISTERED_TAG} when it reaches the area - * where the value is located. - * - * @see #EVENT_VALUE_OF_REGISTERED_TAG - */ - protected void registerForTagValue(ExifTag tag) { - if (tag.getOffset() >= mTiffStream.getReadByteCount()) { - mCorrespondingEvent.put(tag.getOffset(), new ExifTagEvent(tag, true)); - } - } - - private void registerIfd(int ifdType, long offset) { - // Cast unsigned int to int since the offset is always smaller - // than the size of APP1 (65536) - mCorrespondingEvent.put((int) offset, new IfdEvent(ifdType, isIfdRequested(ifdType))); - } - - private void registerCompressedImage(long offset) { - mCorrespondingEvent.put((int) offset, new ImageEvent(EVENT_COMPRESSED_IMAGE)); - } - - private void registerUncompressedStrip(int stripIndex, long offset) { - mCorrespondingEvent.put((int) offset, new ImageEvent(EVENT_UNCOMPRESSED_STRIP - , stripIndex)); - } - - private ExifTag readTag() throws IOException, ExifInvalidFormatException { - short tagId = mTiffStream.readShort(); - short dataFormat = mTiffStream.readShort(); - long numOfComp = mTiffStream.readUnsignedInt(); - if (numOfComp > Integer.MAX_VALUE) { - throw new ExifInvalidFormatException( - "Number of component is larger then Integer.MAX_VALUE"); - } - // Some invalid image file contains invalid data type. Ignore those tags - if (!ExifTag.isValidType(dataFormat)) { - Log.w(TAG, String.format("Tag %04x: Invalid data type %d", tagId, dataFormat)); - mTiffStream.skip(4); - return null; - } - // TODO: handle numOfComp overflow - ExifTag tag = new ExifTag(tagId, dataFormat, (int) numOfComp, mIfdType, - ((int) numOfComp) != ExifTag.SIZE_UNDEFINED); - int dataSize = tag.getDataSize(); - if (dataSize > 4) { - long offset = mTiffStream.readUnsignedInt(); - if (offset > Integer.MAX_VALUE) { - throw new ExifInvalidFormatException( - "offset is larger then Integer.MAX_VALUE"); - } - // Some invalid images put some undefined data before IFD0. - // Read the data here. - if ((offset < mIfd0Position) && (dataFormat == ExifTag.TYPE_UNDEFINED)) { - byte[] buf = new byte[(int) numOfComp]; - System.arraycopy(mDataAboveIfd0, (int) offset - DEFAULT_IFD0_OFFSET, - buf, 0, (int) numOfComp); - tag.setValue(buf); - } else { - tag.setOffset((int) offset); - } - } else { - boolean defCount = tag.hasDefinedCount(); - // Set defined count to 0 so we can add \0 to non-terminated strings - tag.setHasDefinedCount(false); - // Read value - readFullTagValue(tag); - tag.setHasDefinedCount(defCount); - mTiffStream.skip(4 - dataSize); - // Set the offset to the position of value. - tag.setOffset(mTiffStream.getReadByteCount() - 4); - } - return tag; - } - - /** - * Check the tag, if the tag is one of the offset tag that points to the IFD - * or image the caller is interested in, register the IFD or image. - */ - private void checkOffsetOrImageTag(ExifTag tag) { - // Some invalid formattd image contains tag with 0 size. - if (tag.getComponentCount() == 0) { - return; - } - short tid = tag.getTagId(); - int ifd = tag.getIfd(); - if (tid == TAG_EXIF_IFD && checkAllowed(ifd, ExifInterface.TAG_EXIF_IFD)) { - if (isIfdRequested(IfdId.TYPE_IFD_EXIF) - || isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) { - registerIfd(IfdId.TYPE_IFD_EXIF, tag.getValueAt(0)); - } - } else if (tid == TAG_GPS_IFD && checkAllowed(ifd, ExifInterface.TAG_GPS_IFD)) { - if (isIfdRequested(IfdId.TYPE_IFD_GPS)) { - registerIfd(IfdId.TYPE_IFD_GPS, tag.getValueAt(0)); - } - } else if (tid == TAG_INTEROPERABILITY_IFD - && checkAllowed(ifd, ExifInterface.TAG_INTEROPERABILITY_IFD)) { - if (isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) { - registerIfd(IfdId.TYPE_IFD_INTEROPERABILITY, tag.getValueAt(0)); - } - } else if (tid == TAG_JPEG_INTERCHANGE_FORMAT - && checkAllowed(ifd, ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT)) { - if (isThumbnailRequested()) { - registerCompressedImage(tag.getValueAt(0)); - } - } else if (tid == TAG_JPEG_INTERCHANGE_FORMAT_LENGTH - && checkAllowed(ifd, ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH)) { - if (isThumbnailRequested()) { - mJpegSizeTag = tag; - } - } else if (tid == TAG_STRIP_OFFSETS && checkAllowed(ifd, ExifInterface.TAG_STRIP_OFFSETS)) { - if (isThumbnailRequested()) { - if (tag.hasValue()) { - for (int i = 0; i < tag.getComponentCount(); i++) { - if (tag.getDataType() == ExifTag.TYPE_UNSIGNED_SHORT) { - registerUncompressedStrip(i, tag.getValueAt(i)); - } else { - registerUncompressedStrip(i, tag.getValueAt(i)); - } - } - } else { - mCorrespondingEvent.put(tag.getOffset(), new ExifTagEvent(tag, false)); - } - } - } else if (tid == TAG_STRIP_BYTE_COUNTS - && checkAllowed(ifd, ExifInterface.TAG_STRIP_BYTE_COUNTS) - &&isThumbnailRequested() && tag.hasValue()) { - mStripSizeTag = tag; - } - } - - private boolean checkAllowed(int ifd, int tagId) { - int info = mInterface.getTagInfo().get(tagId); - if (info == ExifInterface.DEFINITION_NULL) { - return false; - } - return ExifInterface.isIfdAllowed(info, ifd); - } - - protected void readFullTagValue(ExifTag tag) throws IOException { - // Some invalid images contains tags with wrong size, check it here - short type = tag.getDataType(); - if (type == ExifTag.TYPE_ASCII || type == ExifTag.TYPE_UNDEFINED || - type == ExifTag.TYPE_UNSIGNED_BYTE) { - int size = tag.getComponentCount(); - if (mCorrespondingEvent.size() > 0) { - if (mCorrespondingEvent.firstEntry().getKey() < mTiffStream.getReadByteCount() - + size) { - Object event = mCorrespondingEvent.firstEntry().getValue(); - if (event instanceof ImageEvent) { - // Tag value overlaps thumbnail, ignore thumbnail. - Log.w(TAG, "Thumbnail overlaps value for tag: \n" + tag.toString()); - Entry<Integer, Object> entry = mCorrespondingEvent.pollFirstEntry(); - Log.w(TAG, "Invalid thumbnail offset: " + entry.getKey()); - } else { - // Tag value overlaps another tag, shorten count - if (event instanceof IfdEvent) { - Log.w(TAG, "Ifd " + ((IfdEvent) event).ifd - + " overlaps value for tag: \n" + tag.toString()); - } else if (event instanceof ExifTagEvent) { - Log.w(TAG, "Tag value for tag: \n" - + ((ExifTagEvent) event).tag.toString() - + " overlaps value for tag: \n" + tag.toString()); - } - size = mCorrespondingEvent.firstEntry().getKey() - - mTiffStream.getReadByteCount(); - Log.w(TAG, "Invalid size of tag: \n" + tag.toString() - + " setting count to: " + size); - tag.forceSetComponentCount(size); - } - } - } - } - switch (tag.getDataType()) { - case ExifTag.TYPE_UNSIGNED_BYTE: - case ExifTag.TYPE_UNDEFINED: { - byte buf[] = new byte[tag.getComponentCount()]; - read(buf); - tag.setValue(buf); - } - break; - case ExifTag.TYPE_ASCII: - tag.setValue(readString(tag.getComponentCount())); - break; - case ExifTag.TYPE_UNSIGNED_LONG: { - long value[] = new long[tag.getComponentCount()]; - for (int i = 0, n = value.length; i < n; i++) { - value[i] = readUnsignedLong(); - } - tag.setValue(value); - } - break; - case ExifTag.TYPE_UNSIGNED_RATIONAL: { - Rational value[] = new Rational[tag.getComponentCount()]; - for (int i = 0, n = value.length; i < n; i++) { - value[i] = readUnsignedRational(); - } - tag.setValue(value); - } - break; - case ExifTag.TYPE_UNSIGNED_SHORT: { - int value[] = new int[tag.getComponentCount()]; - for (int i = 0, n = value.length; i < n; i++) { - value[i] = readUnsignedShort(); - } - tag.setValue(value); - } - break; - case ExifTag.TYPE_LONG: { - int value[] = new int[tag.getComponentCount()]; - for (int i = 0, n = value.length; i < n; i++) { - value[i] = readLong(); - } - tag.setValue(value); - } - break; - case ExifTag.TYPE_RATIONAL: { - Rational value[] = new Rational[tag.getComponentCount()]; - for (int i = 0, n = value.length; i < n; i++) { - value[i] = readRational(); - } - tag.setValue(value); - } - break; - } - if (LOGV) { - Log.v(TAG, "\n" + tag.toString()); - } - } - - private void parseTiffHeader() throws IOException, - ExifInvalidFormatException { - short byteOrder = mTiffStream.readShort(); - if (LITTLE_ENDIAN_TAG == byteOrder) { - mTiffStream.setByteOrder(ByteOrder.LITTLE_ENDIAN); - } else if (BIG_ENDIAN_TAG == byteOrder) { - mTiffStream.setByteOrder(ByteOrder.BIG_ENDIAN); - } else { - throw new ExifInvalidFormatException("Invalid TIFF header"); - } - - if (mTiffStream.readShort() != TIFF_HEADER_TAIL) { - throw new ExifInvalidFormatException("Invalid TIFF header"); - } - } - - private boolean seekTiffData(InputStream inputStream) throws IOException, - ExifInvalidFormatException { - CountedDataInputStream dataStream = new CountedDataInputStream(inputStream); - if (dataStream.readShort() != JpegHeader.SOI) { - throw new ExifInvalidFormatException("Invalid JPEG format"); - } - - short marker = dataStream.readShort(); - while (marker != JpegHeader.EOI - && !JpegHeader.isSofMarker(marker)) { - int length = dataStream.readUnsignedShort(); - // Some invalid formatted image contains multiple APP1, - // try to find the one with Exif data. - if (marker == JpegHeader.APP1) { - int header = 0; - short headerTail = 0; - if (length >= 8) { - header = dataStream.readInt(); - headerTail = dataStream.readShort(); - length -= 6; - if (header == EXIF_HEADER && headerTail == EXIF_HEADER_TAIL) { - mTiffStartPosition = dataStream.getReadByteCount(); - mApp1End = length; - mOffsetToApp1EndFromSOF = mTiffStartPosition + mApp1End; - return true; - } - } - } - if (length < 2 || (length - 2) != dataStream.skip(length - 2)) { - Log.w(TAG, "Invalid JPEG format."); - return false; - } - marker = dataStream.readShort(); - } - return false; - } - - protected int getOffsetToExifEndFromSOF() { - return mOffsetToApp1EndFromSOF; - } - - protected int getTiffStartPosition() { - return mTiffStartPosition; - } - - /** - * Reads bytes from the InputStream. - */ - protected int read(byte[] buffer, int offset, int length) throws IOException { - return mTiffStream.read(buffer, offset, length); - } - - /** - * Equivalent to read(buffer, 0, buffer.length). - */ - protected int read(byte[] buffer) throws IOException { - return mTiffStream.read(buffer); - } - - /** - * Reads a String from the InputStream with US-ASCII charset. The parser - * will read n bytes and convert it to ascii string. This is used for - * reading values of type {@link ExifTag#TYPE_ASCII}. - */ - protected String readString(int n) throws IOException { - return readString(n, US_ASCII); - } - - /** - * Reads a String from the InputStream with the given charset. The parser - * will read n bytes and convert it to string. This is used for reading - * values of type {@link ExifTag#TYPE_ASCII}. - */ - protected String readString(int n, Charset charset) throws IOException { - if (n > 0) { - return mTiffStream.readString(n, charset); - } else { - return ""; - } - } - - /** - * Reads value of type {@link ExifTag#TYPE_UNSIGNED_SHORT} from the - * InputStream. - */ - protected int readUnsignedShort() throws IOException { - return mTiffStream.readShort() & 0xffff; - } - - /** - * Reads value of type {@link ExifTag#TYPE_UNSIGNED_LONG} from the - * InputStream. - */ - protected long readUnsignedLong() throws IOException { - return readLong() & 0xffffffffL; - } - - /** - * Reads value of type {@link ExifTag#TYPE_UNSIGNED_RATIONAL} from the - * InputStream. - */ - protected Rational readUnsignedRational() throws IOException { - long nomi = readUnsignedLong(); - long denomi = readUnsignedLong(); - return new Rational(nomi, denomi); - } - - /** - * Reads value of type {@link ExifTag#TYPE_LONG} from the InputStream. - */ - protected int readLong() throws IOException { - return mTiffStream.readInt(); - } - - /** - * Reads value of type {@link ExifTag#TYPE_RATIONAL} from the InputStream. - */ - protected Rational readRational() throws IOException { - int nomi = readLong(); - int denomi = readLong(); - return new Rational(nomi, denomi); - } - - private static class ImageEvent { - int stripIndex; - int type; - - ImageEvent(int type) { - this.stripIndex = 0; - this.type = type; - } - - ImageEvent(int type, int stripIndex) { - this.type = type; - this.stripIndex = stripIndex; - } - } - - private static class IfdEvent { - int ifd; - boolean isRequested; - - IfdEvent(int ifd, boolean isInterestedIfd) { - this.ifd = ifd; - this.isRequested = isInterestedIfd; - } - } - - private static class ExifTagEvent { - ExifTag tag; - boolean isRequested; - - ExifTagEvent(ExifTag tag, boolean isRequireByUser) { - this.tag = tag; - this.isRequested = isRequireByUser; - } - } - - /** - * Gets the byte order of the current InputStream. - */ - protected ByteOrder getByteOrder() { - return mTiffStream.getByteOrder(); - } -} diff --git a/WallpaperPicker/src/com/android/gallery3d/exif/ExifReader.java b/WallpaperPicker/src/com/android/gallery3d/exif/ExifReader.java deleted file mode 100644 index 68e972fb7..000000000 --- a/WallpaperPicker/src/com/android/gallery3d/exif/ExifReader.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.gallery3d.exif; - -import android.util.Log; - -import java.io.IOException; -import java.io.InputStream; - -/** - * This class reads the EXIF header of a JPEG file and stores it in - * {@link ExifData}. - */ -class ExifReader { - private static final String TAG = "ExifReader"; - - private final ExifInterface mInterface; - - ExifReader(ExifInterface iRef) { - mInterface = iRef; - } - - /** - * Parses the inputStream and and returns the EXIF data in an - * {@link ExifData}. - * - * @throws ExifInvalidFormatException - * @throws IOException - */ - protected ExifData read(InputStream inputStream) throws ExifInvalidFormatException, - IOException { - ExifParser parser = ExifParser.parse(inputStream, mInterface); - ExifData exifData = new ExifData(parser.getByteOrder()); - ExifTag tag = null; - - int event = parser.next(); - while (event != ExifParser.EVENT_END) { - switch (event) { - case ExifParser.EVENT_START_OF_IFD: - exifData.addIfdData(new IfdData(parser.getCurrentIfd())); - break; - case ExifParser.EVENT_NEW_TAG: - tag = parser.getTag(); - if (!tag.hasValue()) { - parser.registerForTagValue(tag); - } else { - exifData.getIfdData(tag.getIfd()).setTag(tag); - } - break; - case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG: - tag = parser.getTag(); - if (tag.getDataType() == ExifTag.TYPE_UNDEFINED) { - parser.readFullTagValue(tag); - } - exifData.getIfdData(tag.getIfd()).setTag(tag); - break; - case ExifParser.EVENT_COMPRESSED_IMAGE: - byte buf[] = new byte[parser.getCompressedImageSize()]; - if (buf.length == parser.read(buf)) { - exifData.setCompressedThumbnail(buf); - } else { - Log.w(TAG, "Failed to read the compressed thumbnail"); - } - break; - case ExifParser.EVENT_UNCOMPRESSED_STRIP: - buf = new byte[parser.getStripSize()]; - if (buf.length == parser.read(buf)) { - exifData.setStripBytes(parser.getStripIndex(), buf); - } else { - Log.w(TAG, "Failed to read the strip bytes"); - } - break; - } - event = parser.next(); - } - return exifData; - } -} diff --git a/WallpaperPicker/src/com/android/gallery3d/exif/ExifTag.java b/WallpaperPicker/src/com/android/gallery3d/exif/ExifTag.java deleted file mode 100644 index b8b387201..000000000 --- a/WallpaperPicker/src/com/android/gallery3d/exif/ExifTag.java +++ /dev/null @@ -1,1008 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.gallery3d.exif; - -import java.nio.charset.Charset; -import java.text.SimpleDateFormat; -import java.util.Arrays; -import java.util.Date; - -/** - * This class stores information of an EXIF tag. For more information about - * defined EXIF tags, please read the Jeita EXIF 2.2 standard. Tags should be - * instantiated using {@link ExifInterface#buildTag}. - * - * @see ExifInterface - */ -public class ExifTag { - /** - * The BYTE type in the EXIF standard. An 8-bit unsigned integer. - */ - public static final short TYPE_UNSIGNED_BYTE = 1; - /** - * The ASCII type in the EXIF standard. An 8-bit byte containing one 7-bit - * ASCII code. The final byte is terminated with NULL. - */ - public static final short TYPE_ASCII = 2; - /** - * The SHORT type in the EXIF standard. A 16-bit (2-byte) unsigned integer - */ - public static final short TYPE_UNSIGNED_SHORT = 3; - /** - * The LONG type in the EXIF standard. A 32-bit (4-byte) unsigned integer - */ - public static final short TYPE_UNSIGNED_LONG = 4; - /** - * The RATIONAL type of EXIF standard. It consists of two LONGs. The first - * one is the numerator and the second one expresses the denominator. - */ - public static final short TYPE_UNSIGNED_RATIONAL = 5; - /** - * The UNDEFINED type in the EXIF standard. An 8-bit byte that can take any - * value depending on the field definition. - */ - public static final short TYPE_UNDEFINED = 7; - /** - * The SLONG type in the EXIF standard. A 32-bit (4-byte) signed integer - * (2's complement notation). - */ - public static final short TYPE_LONG = 9; - /** - * The SRATIONAL type of EXIF standard. It consists of two SLONGs. The first - * one is the numerator and the second one is the denominator. - */ - public static final short TYPE_RATIONAL = 10; - - private static Charset US_ASCII = Charset.forName("US-ASCII"); - private static final int TYPE_TO_SIZE_MAP[] = new int[11]; - private static final int UNSIGNED_SHORT_MAX = 65535; - private static final long UNSIGNED_LONG_MAX = 4294967295L; - private static final long LONG_MAX = Integer.MAX_VALUE; - private static final long LONG_MIN = Integer.MIN_VALUE; - - static { - TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_BYTE] = 1; - TYPE_TO_SIZE_MAP[TYPE_ASCII] = 1; - TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_SHORT] = 2; - TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_LONG] = 4; - TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_RATIONAL] = 8; - TYPE_TO_SIZE_MAP[TYPE_UNDEFINED] = 1; - TYPE_TO_SIZE_MAP[TYPE_LONG] = 4; - TYPE_TO_SIZE_MAP[TYPE_RATIONAL] = 8; - } - - static final int SIZE_UNDEFINED = 0; - - // Exif TagId - private final short mTagId; - // Exif Tag Type - private final short mDataType; - // If tag has defined count - private boolean mHasDefinedDefaultComponentCount; - // Actual data count in tag (should be number of elements in value array) - private int mComponentCountActual; - // The ifd that this tag should be put in - private int mIfd; - // The value (array of elements of type Tag Type) - private Object mValue; - // Value offset in exif header. - private int mOffset; - - private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("yyyy:MM:dd kk:mm:ss"); - - /** - * Returns true if the given IFD is a valid IFD. - */ - public static boolean isValidIfd(int ifdId) { - return ifdId == IfdId.TYPE_IFD_0 || ifdId == IfdId.TYPE_IFD_1 - || ifdId == IfdId.TYPE_IFD_EXIF || ifdId == IfdId.TYPE_IFD_INTEROPERABILITY - || ifdId == IfdId.TYPE_IFD_GPS; - } - - /** - * Returns true if a given type is a valid tag type. - */ - public static boolean isValidType(short type) { - return type == TYPE_UNSIGNED_BYTE || type == TYPE_ASCII || - type == TYPE_UNSIGNED_SHORT || type == TYPE_UNSIGNED_LONG || - type == TYPE_UNSIGNED_RATIONAL || type == TYPE_UNDEFINED || - type == TYPE_LONG || type == TYPE_RATIONAL; - } - - // Use builtTag in ExifInterface instead of constructor. - ExifTag(short tagId, short type, int componentCount, int ifd, - boolean hasDefinedComponentCount) { - mTagId = tagId; - mDataType = type; - mComponentCountActual = componentCount; - mHasDefinedDefaultComponentCount = hasDefinedComponentCount; - mIfd = ifd; - mValue = null; - } - - /** - * Gets the element size of the given data type in bytes. - * - * @see #TYPE_ASCII - * @see #TYPE_LONG - * @see #TYPE_RATIONAL - * @see #TYPE_UNDEFINED - * @see #TYPE_UNSIGNED_BYTE - * @see #TYPE_UNSIGNED_LONG - * @see #TYPE_UNSIGNED_RATIONAL - * @see #TYPE_UNSIGNED_SHORT - */ - public static int getElementSize(short type) { - return TYPE_TO_SIZE_MAP[type]; - } - - /** - * Returns the ID of the IFD this tag belongs to. - * - * @see IfdId#TYPE_IFD_0 - * @see IfdId#TYPE_IFD_1 - * @see IfdId#TYPE_IFD_EXIF - * @see IfdId#TYPE_IFD_GPS - * @see IfdId#TYPE_IFD_INTEROPERABILITY - */ - public int getIfd() { - return mIfd; - } - - protected void setIfd(int ifdId) { - mIfd = ifdId; - } - - /** - * Gets the TID of this tag. - */ - public short getTagId() { - return mTagId; - } - - /** - * Gets the data type of this tag - * - * @see #TYPE_ASCII - * @see #TYPE_LONG - * @see #TYPE_RATIONAL - * @see #TYPE_UNDEFINED - * @see #TYPE_UNSIGNED_BYTE - * @see #TYPE_UNSIGNED_LONG - * @see #TYPE_UNSIGNED_RATIONAL - * @see #TYPE_UNSIGNED_SHORT - */ - public short getDataType() { - return mDataType; - } - - /** - * Gets the total data size in bytes of the value of this tag. - */ - public int getDataSize() { - return getComponentCount() * getElementSize(getDataType()); - } - - /** - * Gets the component count of this tag. - */ - - // TODO: fix integer overflows with this - public int getComponentCount() { - return mComponentCountActual; - } - - /** - * Sets the component count of this tag. Call this function before - * setValue() if the length of value does not match the component count. - */ - protected void forceSetComponentCount(int count) { - mComponentCountActual = count; - } - - /** - * Returns true if this ExifTag contains value; otherwise, this tag will - * contain an offset value that is determined when the tag is written. - */ - public boolean hasValue() { - return mValue != null; - } - - /** - * Sets integer values into this tag. This method should be used for tags of - * type {@link #TYPE_UNSIGNED_SHORT}. This method will fail if: - * <ul> - * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_SHORT}, - * {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_LONG}.</li> - * <li>The value overflows.</li> - * <li>The value.length does NOT match the component count in the definition - * for this tag.</li> - * </ul> - */ - public boolean setValue(int[] value) { - if (checkBadComponentCount(value.length)) { - return false; - } - if (mDataType != TYPE_UNSIGNED_SHORT && mDataType != TYPE_LONG && - mDataType != TYPE_UNSIGNED_LONG) { - return false; - } - if (mDataType == TYPE_UNSIGNED_SHORT && checkOverflowForUnsignedShort(value)) { - return false; - } else if (mDataType == TYPE_UNSIGNED_LONG && checkOverflowForUnsignedLong(value)) { - return false; - } - - long[] data = new long[value.length]; - for (int i = 0; i < value.length; i++) { - data[i] = value[i]; - } - mValue = data; - mComponentCountActual = value.length; - return true; - } - - /** - * Sets integer value into this tag. This method should be used for tags of - * type {@link #TYPE_UNSIGNED_SHORT}, or {@link #TYPE_LONG}. This method - * will fail if: - * <ul> - * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_SHORT}, - * {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_LONG}.</li> - * <li>The value overflows.</li> - * <li>The component count in the definition of this tag is not 1.</li> - * </ul> - */ - public boolean setValue(int value) { - return setValue(new int[] { - value - }); - } - - /** - * Sets long values into this tag. This method should be used for tags of - * type {@link #TYPE_UNSIGNED_LONG}. This method will fail if: - * <ul> - * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_LONG}.</li> - * <li>The value overflows.</li> - * <li>The value.length does NOT match the component count in the definition - * for this tag.</li> - * </ul> - */ - public boolean setValue(long[] value) { - if (checkBadComponentCount(value.length) || mDataType != TYPE_UNSIGNED_LONG) { - return false; - } - if (checkOverflowForUnsignedLong(value)) { - return false; - } - mValue = value; - mComponentCountActual = value.length; - return true; - } - - /** - * Sets long values into this tag. This method should be used for tags of - * type {@link #TYPE_UNSIGNED_LONG}. This method will fail if: - * <ul> - * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_LONG}.</li> - * <li>The value overflows.</li> - * <li>The component count in the definition for this tag is not 1.</li> - * </ul> - */ - public boolean setValue(long value) { - return setValue(new long[] { - value - }); - } - - /** - * Sets a string value into this tag. This method should be used for tags of - * type {@link #TYPE_ASCII}. The string is converted to an ASCII string. - * Characters that cannot be converted are replaced with '?'. The length of - * the string must be equal to either (component count -1) or (component - * count). The final byte will be set to the string null terminator '\0', - * overwriting the last character in the string if the value.length is equal - * to the component count. This method will fail if: - * <ul> - * <li>The data type is not {@link #TYPE_ASCII} or {@link #TYPE_UNDEFINED}.</li> - * <li>The length of the string is not equal to (component count -1) or - * (component count) in the definition for this tag.</li> - * </ul> - */ - public boolean setValue(String value) { - if (mDataType != TYPE_ASCII && mDataType != TYPE_UNDEFINED) { - return false; - } - - byte[] buf = value.getBytes(US_ASCII); - byte[] finalBuf = buf; - if (buf.length > 0) { - finalBuf = (buf[buf.length - 1] == 0 || mDataType == TYPE_UNDEFINED) ? buf : Arrays - .copyOf(buf, buf.length + 1); - } else if (mDataType == TYPE_ASCII && mComponentCountActual == 1) { - finalBuf = new byte[] { 0 }; - } - int count = finalBuf.length; - if (checkBadComponentCount(count)) { - return false; - } - mComponentCountActual = count; - mValue = finalBuf; - return true; - } - - /** - * Sets Rational values into this tag. This method should be used for tags - * of type {@link #TYPE_UNSIGNED_RATIONAL}, or {@link #TYPE_RATIONAL}. This - * method will fail if: - * <ul> - * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_RATIONAL} - * or {@link #TYPE_RATIONAL}.</li> - * <li>The value overflows.</li> - * <li>The value.length does NOT match the component count in the definition - * for this tag.</li> - * </ul> - * - * @see Rational - */ - public boolean setValue(Rational[] value) { - if (checkBadComponentCount(value.length)) { - return false; - } - if (mDataType != TYPE_UNSIGNED_RATIONAL && mDataType != TYPE_RATIONAL) { - return false; - } - if (mDataType == TYPE_UNSIGNED_RATIONAL && checkOverflowForUnsignedRational(value)) { - return false; - } else if (mDataType == TYPE_RATIONAL && checkOverflowForRational(value)) { - return false; - } - - mValue = value; - mComponentCountActual = value.length; - return true; - } - - /** - * Sets a Rational value into this tag. This method should be used for tags - * of type {@link #TYPE_UNSIGNED_RATIONAL}, or {@link #TYPE_RATIONAL}. This - * method will fail if: - * <ul> - * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_RATIONAL} - * or {@link #TYPE_RATIONAL}.</li> - * <li>The value overflows.</li> - * <li>The component count in the definition for this tag is not 1.</li> - * </ul> - * - * @see Rational - */ - public boolean setValue(Rational value) { - return setValue(new Rational[] { - value - }); - } - - /** - * Sets byte values into this tag. This method should be used for tags of - * type {@link #TYPE_UNSIGNED_BYTE} or {@link #TYPE_UNDEFINED}. This method - * will fail if: - * <ul> - * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_BYTE} or - * {@link #TYPE_UNDEFINED} .</li> - * <li>The length does NOT match the component count in the definition for - * this tag.</li> - * </ul> - */ - public boolean setValue(byte[] value, int offset, int length) { - if (checkBadComponentCount(length)) { - return false; - } - if (mDataType != TYPE_UNSIGNED_BYTE && mDataType != TYPE_UNDEFINED) { - return false; - } - mValue = new byte[length]; - System.arraycopy(value, offset, mValue, 0, length); - mComponentCountActual = length; - return true; - } - - /** - * Equivalent to setValue(value, 0, value.length). - */ - public boolean setValue(byte[] value) { - return setValue(value, 0, value.length); - } - - /** - * Sets byte value into this tag. This method should be used for tags of - * type {@link #TYPE_UNSIGNED_BYTE} or {@link #TYPE_UNDEFINED}. This method - * will fail if: - * <ul> - * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_BYTE} or - * {@link #TYPE_UNDEFINED} .</li> - * <li>The component count in the definition for this tag is not 1.</li> - * </ul> - */ - public boolean setValue(byte value) { - return setValue(new byte[] { - value - }); - } - - /** - * Sets the value for this tag using an appropriate setValue method for the - * given object. This method will fail if: - * <ul> - * <li>The corresponding setValue method for the class of the object passed - * in would fail.</li> - * <li>There is no obvious way to cast the object passed in into an EXIF tag - * type.</li> - * </ul> - */ - public boolean setValue(Object obj) { - if (obj == null) { - return false; - } else if (obj instanceof Short) { - return setValue(((Short) obj).shortValue() & 0x0ffff); - } else if (obj instanceof String) { - return setValue((String) obj); - } else if (obj instanceof int[]) { - return setValue((int[]) obj); - } else if (obj instanceof long[]) { - return setValue((long[]) obj); - } else if (obj instanceof Rational) { - return setValue((Rational) obj); - } else if (obj instanceof Rational[]) { - return setValue((Rational[]) obj); - } else if (obj instanceof byte[]) { - return setValue((byte[]) obj); - } else if (obj instanceof Integer) { - return setValue(((Integer) obj).intValue()); - } else if (obj instanceof Long) { - return setValue(((Long) obj).longValue()); - } else if (obj instanceof Byte) { - return setValue(((Byte) obj).byteValue()); - } else if (obj instanceof Short[]) { - // Nulls in this array are treated as zeroes. - Short[] arr = (Short[]) obj; - int[] fin = new int[arr.length]; - for (int i = 0; i < arr.length; i++) { - fin[i] = (arr[i] == null) ? 0 : arr[i].shortValue() & 0x0ffff; - } - return setValue(fin); - } else if (obj instanceof Integer[]) { - // Nulls in this array are treated as zeroes. - Integer[] arr = (Integer[]) obj; - int[] fin = new int[arr.length]; - for (int i = 0; i < arr.length; i++) { - fin[i] = (arr[i] == null) ? 0 : arr[i].intValue(); - } - return setValue(fin); - } else if (obj instanceof Long[]) { - // Nulls in this array are treated as zeroes. - Long[] arr = (Long[]) obj; - long[] fin = new long[arr.length]; - for (int i = 0; i < arr.length; i++) { - fin[i] = (arr[i] == null) ? 0 : arr[i].longValue(); - } - return setValue(fin); - } else if (obj instanceof Byte[]) { - // Nulls in this array are treated as zeroes. - Byte[] arr = (Byte[]) obj; - byte[] fin = new byte[arr.length]; - for (int i = 0; i < arr.length; i++) { - fin[i] = (arr[i] == null) ? 0 : arr[i].byteValue(); - } - return setValue(fin); - } else { - return false; - } - } - - /** - * Sets a timestamp to this tag. The method converts the timestamp with the - * format of "yyyy:MM:dd kk:mm:ss" and calls {@link #setValue(String)}. This - * method will fail if the data type is not {@link #TYPE_ASCII} or the - * component count of this tag is not 20 or undefined. - * - * @param time the number of milliseconds since Jan. 1, 1970 GMT - * @return true on success - */ - public boolean setTimeValue(long time) { - // synchronized on TIME_FORMAT as SimpleDateFormat is not thread safe - synchronized (TIME_FORMAT) { - return setValue(TIME_FORMAT.format(new Date(time))); - } - } - - /** - * Gets the value as a String. This method should be used for tags of type - * {@link #TYPE_ASCII}. - * - * @return the value as a String, or null if the tag's value does not exist - * or cannot be converted to a String. - */ - public String getValueAsString() { - if (mValue == null) { - return null; - } else if (mValue instanceof String) { - return (String) mValue; - } else if (mValue instanceof byte[]) { - return new String((byte[]) mValue, US_ASCII); - } - return null; - } - - /** - * Gets the value as a String. This method should be used for tags of type - * {@link #TYPE_ASCII}. - * - * @param defaultValue the String to return if the tag's value does not - * exist or cannot be converted to a String. - * @return the tag's value as a String, or the defaultValue. - */ - public String getValueAsString(String defaultValue) { - String s = getValueAsString(); - if (s == null) { - return defaultValue; - } - return s; - } - - /** - * Gets the value as a byte array. This method should be used for tags of - * type {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE}. - * - * @return the value as a byte array, or null if the tag's value does not - * exist or cannot be converted to a byte array. - */ - public byte[] getValueAsBytes() { - if (mValue instanceof byte[]) { - return (byte[]) mValue; - } - return null; - } - - /** - * Gets the value as a byte. If there are more than 1 bytes in this value, - * gets the first byte. This method should be used for tags of type - * {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE}. - * - * @param defaultValue the byte to return if tag's value does not exist or - * cannot be converted to a byte. - * @return the tag's value as a byte, or the defaultValue. - */ - public byte getValueAsByte(byte defaultValue) { - byte[] b = getValueAsBytes(); - if (b == null || b.length < 1) { - return defaultValue; - } - return b[0]; - } - - /** - * Gets the value as an array of Rationals. This method should be used for - * tags of type {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}. - * - * @return the value as as an array of Rationals, or null if the tag's value - * does not exist or cannot be converted to an array of Rationals. - */ - public Rational[] getValueAsRationals() { - if (mValue instanceof Rational[]) { - return (Rational[]) mValue; - } - return null; - } - - /** - * Gets the value as a Rational. If there are more than 1 Rationals in this - * value, gets the first one. This method should be used for tags of type - * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}. - * - * @param defaultValue the Rational to return if tag's value does not exist - * or cannot be converted to a Rational. - * @return the tag's value as a Rational, or the defaultValue. - */ - public Rational getValueAsRational(Rational defaultValue) { - Rational[] r = getValueAsRationals(); - if (r == null || r.length < 1) { - return defaultValue; - } - return r[0]; - } - - /** - * Gets the value as a Rational. If there are more than 1 Rationals in this - * value, gets the first one. This method should be used for tags of type - * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}. - * - * @param defaultValue the numerator of the Rational to return if tag's - * value does not exist or cannot be converted to a Rational (the - * denominator will be 1). - * @return the tag's value as a Rational, or the defaultValue. - */ - public Rational getValueAsRational(long defaultValue) { - Rational defaultVal = new Rational(defaultValue, 1); - return getValueAsRational(defaultVal); - } - - /** - * Gets the value as an array of ints. This method should be used for tags - * of type {@link #TYPE_UNSIGNED_SHORT}, {@link #TYPE_UNSIGNED_LONG}. - * - * @return the value as as an array of ints, or null if the tag's value does - * not exist or cannot be converted to an array of ints. - */ - public int[] getValueAsInts() { - if (mValue == null) { - return null; - } else if (mValue instanceof long[]) { - long[] val = (long[]) mValue; - int[] arr = new int[val.length]; - for (int i = 0; i < val.length; i++) { - arr[i] = (int) val[i]; // Truncates - } - return arr; - } - return null; - } - - /** - * Gets the value as an int. If there are more than 1 ints in this value, - * gets the first one. This method should be used for tags of type - * {@link #TYPE_UNSIGNED_SHORT}, {@link #TYPE_UNSIGNED_LONG}. - * - * @param defaultValue the int to return if tag's value does not exist or - * cannot be converted to an int. - * @return the tag's value as a int, or the defaultValue. - */ - public int getValueAsInt(int defaultValue) { - int[] i = getValueAsInts(); - if (i == null || i.length < 1) { - return defaultValue; - } - return i[0]; - } - - /** - * Gets the value as an array of longs. This method should be used for tags - * of type {@link #TYPE_UNSIGNED_LONG}. - * - * @return the value as as an array of longs, or null if the tag's value - * does not exist or cannot be converted to an array of longs. - */ - public long[] getValueAsLongs() { - if (mValue instanceof long[]) { - return (long[]) mValue; - } - return null; - } - - /** - * Gets the value or null if none exists. If there are more than 1 longs in - * this value, gets the first one. This method should be used for tags of - * type {@link #TYPE_UNSIGNED_LONG}. - * - * @param defaultValue the long to return if tag's value does not exist or - * cannot be converted to a long. - * @return the tag's value as a long, or the defaultValue. - */ - public long getValueAsLong(long defaultValue) { - long[] l = getValueAsLongs(); - if (l == null || l.length < 1) { - return defaultValue; - } - return l[0]; - } - - /** - * Gets the tag's value or null if none exists. - */ - public Object getValue() { - return mValue; - } - - /** - * Gets a long representation of the value. - * - * @param defaultValue value to return if there is no value or value is a - * rational with a denominator of 0. - * @return the tag's value as a long, or defaultValue if no representation - * exists. - */ - public long forceGetValueAsLong(long defaultValue) { - long[] l = getValueAsLongs(); - if (l != null && l.length >= 1) { - return l[0]; - } - byte[] b = getValueAsBytes(); - if (b != null && b.length >= 1) { - return b[0]; - } - Rational[] r = getValueAsRationals(); - if (r != null && r.length >= 1 && r[0].getDenominator() != 0) { - return (long) r[0].toDouble(); - } - return defaultValue; - } - - /** - * Gets a string representation of the value. - */ - public String forceGetValueAsString() { - if (mValue == null) { - return ""; - } else if (mValue instanceof byte[]) { - if (mDataType == TYPE_ASCII) { - return new String((byte[]) mValue, US_ASCII); - } else { - return Arrays.toString((byte[]) mValue); - } - } else if (mValue instanceof long[]) { - if (((long[]) mValue).length == 1) { - return String.valueOf(((long[]) mValue)[0]); - } else { - return Arrays.toString((long[]) mValue); - } - } else if (mValue instanceof Object[]) { - if (((Object[]) mValue).length == 1) { - Object val = ((Object[]) mValue)[0]; - if (val == null) { - return ""; - } else { - return val.toString(); - } - } else { - return Arrays.toString((Object[]) mValue); - } - } else { - return mValue.toString(); - } - } - - /** - * Gets the value for type {@link #TYPE_ASCII}, {@link #TYPE_LONG}, - * {@link #TYPE_UNDEFINED}, {@link #TYPE_UNSIGNED_BYTE}, - * {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_UNSIGNED_SHORT}. For - * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}, call - * {@link #getRational(int)} instead. - * - * @exception IllegalArgumentException if the data type is - * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}. - */ - protected long getValueAt(int index) { - if (mValue instanceof long[]) { - return ((long[]) mValue)[index]; - } else if (mValue instanceof byte[]) { - return ((byte[]) mValue)[index]; - } - throw new IllegalArgumentException("Cannot get integer value from " - + convertTypeToString(mDataType)); - } - - /** - * Gets the {@link #TYPE_ASCII} data. - * - * @exception IllegalArgumentException If the type is NOT - * {@link #TYPE_ASCII}. - */ - protected String getString() { - if (mDataType != TYPE_ASCII) { - throw new IllegalArgumentException("Cannot get ASCII value from " - + convertTypeToString(mDataType)); - } - return new String((byte[]) mValue, US_ASCII); - } - - /* - * Get the converted ascii byte. Used by ExifOutputStream. - */ - protected byte[] getStringByte() { - return (byte[]) mValue; - } - - /** - * Gets the {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL} data. - * - * @exception IllegalArgumentException If the type is NOT - * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}. - */ - protected Rational getRational(int index) { - if ((mDataType != TYPE_RATIONAL) && (mDataType != TYPE_UNSIGNED_RATIONAL)) { - throw new IllegalArgumentException("Cannot get RATIONAL value from " - + convertTypeToString(mDataType)); - } - return ((Rational[]) mValue)[index]; - } - - /** - * Equivalent to getBytes(buffer, 0, buffer.length). - */ - protected void getBytes(byte[] buf) { - getBytes(buf, 0, buf.length); - } - - /** - * Gets the {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE} data. - * - * @param buf the byte array in which to store the bytes read. - * @param offset the initial position in buffer to store the bytes. - * @param length the maximum number of bytes to store in buffer. If length > - * component count, only the valid bytes will be stored. - * @exception IllegalArgumentException If the type is NOT - * {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE}. - */ - protected void getBytes(byte[] buf, int offset, int length) { - if ((mDataType != TYPE_UNDEFINED) && (mDataType != TYPE_UNSIGNED_BYTE)) { - throw new IllegalArgumentException("Cannot get BYTE value from " - + convertTypeToString(mDataType)); - } - System.arraycopy(mValue, 0, buf, offset, - (length > mComponentCountActual) ? mComponentCountActual : length); - } - - /** - * Gets the offset of this tag. This is only valid if this data size > 4 and - * contains an offset to the location of the actual value. - */ - protected int getOffset() { - return mOffset; - } - - /** - * Sets the offset of this tag. - */ - protected void setOffset(int offset) { - mOffset = offset; - } - - protected void setHasDefinedCount(boolean d) { - mHasDefinedDefaultComponentCount = d; - } - - protected boolean hasDefinedCount() { - return mHasDefinedDefaultComponentCount; - } - - private boolean checkBadComponentCount(int count) { - if (mHasDefinedDefaultComponentCount && (mComponentCountActual != count)) { - return true; - } - return false; - } - - private static String convertTypeToString(short type) { - switch (type) { - case TYPE_UNSIGNED_BYTE: - return "UNSIGNED_BYTE"; - case TYPE_ASCII: - return "ASCII"; - case TYPE_UNSIGNED_SHORT: - return "UNSIGNED_SHORT"; - case TYPE_UNSIGNED_LONG: - return "UNSIGNED_LONG"; - case TYPE_UNSIGNED_RATIONAL: - return "UNSIGNED_RATIONAL"; - case TYPE_UNDEFINED: - return "UNDEFINED"; - case TYPE_LONG: - return "LONG"; - case TYPE_RATIONAL: - return "RATIONAL"; - default: - return ""; - } - } - - private boolean checkOverflowForUnsignedShort(int[] value) { - for (int v : value) { - if (v > UNSIGNED_SHORT_MAX || v < 0) { - return true; - } - } - return false; - } - - private boolean checkOverflowForUnsignedLong(long[] value) { - for (long v : value) { - if (v < 0 || v > UNSIGNED_LONG_MAX) { - return true; - } - } - return false; - } - - private boolean checkOverflowForUnsignedLong(int[] value) { - for (int v : value) { - if (v < 0) { - return true; - } - } - return false; - } - - private boolean checkOverflowForUnsignedRational(Rational[] value) { - for (Rational v : value) { - if (v.getNumerator() < 0 || v.getDenominator() < 0 - || v.getNumerator() > UNSIGNED_LONG_MAX - || v.getDenominator() > UNSIGNED_LONG_MAX) { - return true; - } - } - return false; - } - - private boolean checkOverflowForRational(Rational[] value) { - for (Rational v : value) { - if (v.getNumerator() < LONG_MIN || v.getDenominator() < LONG_MIN - || v.getNumerator() > LONG_MAX - || v.getDenominator() > LONG_MAX) { - return true; - } - } - return false; - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (obj instanceof ExifTag) { - ExifTag tag = (ExifTag) obj; - if (tag.mTagId != this.mTagId - || tag.mComponentCountActual != this.mComponentCountActual - || tag.mDataType != this.mDataType) { - return false; - } - if (mValue != null) { - if (tag.mValue == null) { - return false; - } else if (mValue instanceof long[]) { - if (!(tag.mValue instanceof long[])) { - return false; - } - return Arrays.equals((long[]) mValue, (long[]) tag.mValue); - } else if (mValue instanceof Rational[]) { - if (!(tag.mValue instanceof Rational[])) { - return false; - } - return Arrays.equals((Rational[]) mValue, (Rational[]) tag.mValue); - } else if (mValue instanceof byte[]) { - if (!(tag.mValue instanceof byte[])) { - return false; - } - return Arrays.equals((byte[]) mValue, (byte[]) tag.mValue); - } else { - return mValue.equals(tag.mValue); - } - } else { - return tag.mValue == null; - } - } - return false; - } - - @Override - public String toString() { - return String.format("tag id: %04X\n", mTagId) + "ifd id: " + mIfd + "\ntype: " - + convertTypeToString(mDataType) + "\ncount: " + mComponentCountActual - + "\noffset: " + mOffset + "\nvalue: " + forceGetValueAsString() + "\n"; - } - -} diff --git a/WallpaperPicker/src/com/android/gallery3d/exif/IfdData.java b/WallpaperPicker/src/com/android/gallery3d/exif/IfdData.java deleted file mode 100644 index 093944aec..000000000 --- a/WallpaperPicker/src/com/android/gallery3d/exif/IfdData.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.gallery3d.exif; - -import java.util.HashMap; -import java.util.Map; - -/** - * This class stores all the tags in an IFD. - * - * @see ExifData - * @see ExifTag - */ -class IfdData { - - private final int mIfdId; - private final Map<Short, ExifTag> mExifTags = new HashMap<Short, ExifTag>(); - private int mOffsetToNextIfd = 0; - private static final int[] sIfds = { - IfdId.TYPE_IFD_0, IfdId.TYPE_IFD_1, IfdId.TYPE_IFD_EXIF, - IfdId.TYPE_IFD_INTEROPERABILITY, IfdId.TYPE_IFD_GPS - }; - /** - * Creates an IfdData with given IFD ID. - * - * @see IfdId#TYPE_IFD_0 - * @see IfdId#TYPE_IFD_1 - * @see IfdId#TYPE_IFD_EXIF - * @see IfdId#TYPE_IFD_GPS - * @see IfdId#TYPE_IFD_INTEROPERABILITY - */ - IfdData(int ifdId) { - mIfdId = ifdId; - } - - static protected int[] getIfds() { - return sIfds; - } - - /** - * Get a array the contains all {@link ExifTag} in this IFD. - */ - protected ExifTag[] getAllTags() { - return mExifTags.values().toArray(new ExifTag[mExifTags.size()]); - } - - /** - * Gets the ID of this IFD. - * - * @see IfdId#TYPE_IFD_0 - * @see IfdId#TYPE_IFD_1 - * @see IfdId#TYPE_IFD_EXIF - * @see IfdId#TYPE_IFD_GPS - * @see IfdId#TYPE_IFD_INTEROPERABILITY - */ - protected int getId() { - return mIfdId; - } - - /** - * Gets the {@link ExifTag} with given tag id. Return null if there is no - * such tag. - */ - protected ExifTag getTag(short tagId) { - return mExifTags.get(tagId); - } - - /** - * Adds or replaces a {@link ExifTag}. - */ - protected ExifTag setTag(ExifTag tag) { - tag.setIfd(mIfdId); - return mExifTags.put(tag.getTagId(), tag); - } - - protected boolean checkCollision(short tagId) { - return mExifTags.get(tagId) != null; - } - - /** - * Removes the tag of the given ID - */ - protected void removeTag(short tagId) { - mExifTags.remove(tagId); - } - - /** - * Gets the tags count in the IFD. - */ - protected int getTagCount() { - return mExifTags.size(); - } - - /** - * Sets the offset of next IFD. - */ - protected void setOffsetToNextIfd(int offset) { - mOffsetToNextIfd = offset; - } - - /** - * Gets the offset of next IFD. - */ - protected int getOffsetToNextIfd() { - return mOffsetToNextIfd; - } - - /** - * Returns true if all tags in this two IFDs are equal. Note that tags of - * IFDs offset or thumbnail offset will be ignored. - */ - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (obj instanceof IfdData) { - IfdData data = (IfdData) obj; - if (data.getId() == mIfdId && data.getTagCount() == getTagCount()) { - ExifTag[] tags = data.getAllTags(); - for (ExifTag tag : tags) { - if (ExifInterface.isOffsetTag(tag.getTagId())) { - continue; - } - ExifTag tag2 = mExifTags.get(tag.getTagId()); - if (!tag.equals(tag2)) { - return false; - } - } - return true; - } - } - return false; - } -} diff --git a/WallpaperPicker/src/com/android/gallery3d/exif/IfdId.java b/WallpaperPicker/src/com/android/gallery3d/exif/IfdId.java deleted file mode 100644 index 7842edbd4..000000000 --- a/WallpaperPicker/src/com/android/gallery3d/exif/IfdId.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.gallery3d.exif; - -/** - * The constants of the IFD ID defined in EXIF spec. - */ -public interface IfdId { - public static final int TYPE_IFD_0 = 0; - public static final int TYPE_IFD_1 = 1; - public static final int TYPE_IFD_EXIF = 2; - public static final int TYPE_IFD_INTEROPERABILITY = 3; - public static final int TYPE_IFD_GPS = 4; - /* This is used in ExifData to allocate enough IfdData */ - static final int TYPE_IFD_COUNT = 5; - -} diff --git a/WallpaperPicker/src/com/android/gallery3d/exif/JpegHeader.java b/WallpaperPicker/src/com/android/gallery3d/exif/JpegHeader.java deleted file mode 100644 index e3e787eff..000000000 --- a/WallpaperPicker/src/com/android/gallery3d/exif/JpegHeader.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.gallery3d.exif; - -class JpegHeader { - public static final short SOI = (short) 0xFFD8; - public static final short APP1 = (short) 0xFFE1; - public static final short APP0 = (short) 0xFFE0; - public static final short EOI = (short) 0xFFD9; - - /** - * SOF (start of frame). All value between SOF0 and SOF15 is SOF marker except for DHT, JPG, - * and DAC marker. - */ - public static final short SOF0 = (short) 0xFFC0; - public static final short SOF15 = (short) 0xFFCF; - public static final short DHT = (short) 0xFFC4; - public static final short JPG = (short) 0xFFC8; - public static final short DAC = (short) 0xFFCC; - - public static final boolean isSofMarker(short marker) { - return marker >= SOF0 && marker <= SOF15 && marker != DHT && marker != JPG - && marker != DAC; - } -} diff --git a/WallpaperPicker/src/com/android/gallery3d/exif/OrderedDataOutputStream.java b/WallpaperPicker/src/com/android/gallery3d/exif/OrderedDataOutputStream.java deleted file mode 100644 index 428e6b9fc..000000000 --- a/WallpaperPicker/src/com/android/gallery3d/exif/OrderedDataOutputStream.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.gallery3d.exif; - -import java.io.FilterOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; - -class OrderedDataOutputStream extends FilterOutputStream { - private final ByteBuffer mByteBuffer = ByteBuffer.allocate(4); - - public OrderedDataOutputStream(OutputStream out) { - super(out); - } - - public OrderedDataOutputStream setByteOrder(ByteOrder order) { - mByteBuffer.order(order); - return this; - } - - public OrderedDataOutputStream writeShort(short value) throws IOException { - mByteBuffer.rewind(); - mByteBuffer.putShort(value); - out.write(mByteBuffer.array(), 0, 2); - return this; - } - - public OrderedDataOutputStream writeInt(int value) throws IOException { - mByteBuffer.rewind(); - mByteBuffer.putInt(value); - out.write(mByteBuffer.array()); - return this; - } - - public OrderedDataOutputStream writeRational(Rational rational) throws IOException { - writeInt((int) rational.getNumerator()); - writeInt((int) rational.getDenominator()); - return this; - } -} diff --git a/WallpaperPicker/src/com/android/gallery3d/exif/Rational.java b/WallpaperPicker/src/com/android/gallery3d/exif/Rational.java deleted file mode 100644 index 591d63faf..000000000 --- a/WallpaperPicker/src/com/android/gallery3d/exif/Rational.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.gallery3d.exif; - -/** - * The rational data type of EXIF tag. Contains a pair of longs representing the - * numerator and denominator of a Rational number. - */ -public class Rational { - - private final long mNumerator; - private final long mDenominator; - - /** - * Create a Rational with a given numerator and denominator. - * - * @param nominator - * @param denominator - */ - public Rational(long nominator, long denominator) { - mNumerator = nominator; - mDenominator = denominator; - } - - /** - * Create a copy of a Rational. - */ - public Rational(Rational r) { - mNumerator = r.mNumerator; - mDenominator = r.mDenominator; - } - - /** - * Gets the numerator of the rational. - */ - public long getNumerator() { - return mNumerator; - } - - /** - * Gets the denominator of the rational - */ - public long getDenominator() { - return mDenominator; - } - - /** - * Gets the rational value as type double. Will cause a divide-by-zero error - * if the denominator is 0. - */ - public double toDouble() { - return mNumerator / (double) mDenominator; - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (this == obj) { - return true; - } - if (obj instanceof Rational) { - Rational data = (Rational) obj; - return mNumerator == data.mNumerator && mDenominator == data.mDenominator; - } - return false; - } - - @Override - public String toString() { - return mNumerator + "/" + mDenominator; - } -} diff --git a/WallpaperPicker/src/com/android/gallery3d/glrenderer/BasicTexture.java b/WallpaperPicker/src/com/android/gallery3d/glrenderer/BasicTexture.java index 0f3efb727..7270e88c0 100644 --- a/WallpaperPicker/src/com/android/gallery3d/glrenderer/BasicTexture.java +++ b/WallpaperPicker/src/com/android/gallery3d/glrenderer/BasicTexture.java @@ -46,8 +46,6 @@ public abstract class BasicTexture implements Texture { protected int mTextureWidth; protected int mTextureHeight; - private boolean mHasBorder; - protected GLCanvas mCanvasRef = null; private static WeakHashMap<BasicTexture, Object> sAllTextures = new WeakHashMap<BasicTexture, Object>(); @@ -85,10 +83,6 @@ public abstract class BasicTexture implements Texture { } } - public boolean isFlippedVertically() { - return false; - } - public int getId() { return mId; } @@ -113,25 +107,6 @@ public abstract class BasicTexture implements Texture { return mTextureHeight; } - // Returns true if the texture has one pixel transparent border around the - // actual content. This is used to avoid jigged edges. - // - // The jigged edges appear because we use GL_CLAMP_TO_EDGE for texture wrap - // mode (GL_CLAMP is not available in OpenGL ES), so a pixel partially - // covered by the texture will use the color of the edge texel. If we add - // the transparent border, the color of the edge texel will be mixed with - // appropriate amount of transparent. - // - // Currently our background is black, so we can draw the thumbnails without - // enabling blending. - public boolean hasBorder() { - return mHasBorder; - } - - protected void setBorder(boolean hasBorder) { - mHasBorder = hasBorder; - } - @Override public void draw(GLCanvas canvas, int x, int y) { canvas.drawTexture(this, x, y, getWidth(), getHeight()); @@ -146,9 +121,6 @@ public abstract class BasicTexture implements Texture { // It should make sure the data is uploaded to GL memory. abstract protected boolean onBind(GLCanvas canvas); - // Returns the GL texture target for this texture (e.g. GL_TEXTURE_2D). - abstract protected int getTarget(); - public boolean isLoaded() { return mState == STATE_LOADED; } @@ -185,13 +157,6 @@ public abstract class BasicTexture implements Texture { sInFinalizer.set(null); } - // This is for deciding if we can call Bitmap's recycle(). - // We cannot call Bitmap's recycle() in finalizer because at that point - // the finalizer of Bitmap may already be called so recycle() will crash. - public static boolean inFinalizer() { - return sInFinalizer.get() != null; - } - public static void yieldAllTextures() { synchronized (sAllTextures) { for (BasicTexture t : sAllTextures.keySet()) { diff --git a/WallpaperPicker/src/com/android/gallery3d/glrenderer/BitmapTexture.java b/WallpaperPicker/src/com/android/gallery3d/glrenderer/BitmapTexture.java index f8b01cb42..bb69b6858 100644 --- a/WallpaperPicker/src/com/android/gallery3d/glrenderer/BitmapTexture.java +++ b/WallpaperPicker/src/com/android/gallery3d/glrenderer/BitmapTexture.java @@ -29,11 +29,7 @@ public class BitmapTexture extends UploadedTexture { protected Bitmap mContentBitmap; public BitmapTexture(Bitmap bitmap) { - this(bitmap, false); - } - - public BitmapTexture(Bitmap bitmap, boolean hasBorder) { - super(hasBorder); + super(); Utils.assertTrue(bitmap != null && !bitmap.isRecycled()); mContentBitmap = bitmap; } diff --git a/WallpaperPicker/src/com/android/gallery3d/glrenderer/GLCanvas.java b/WallpaperPicker/src/com/android/gallery3d/glrenderer/GLCanvas.java index 5b0747704..2bda8d21a 100644 --- a/WallpaperPicker/src/com/android/gallery3d/glrenderer/GLCanvas.java +++ b/WallpaperPicker/src/com/android/gallery3d/glrenderer/GLCanvas.java @@ -17,7 +17,6 @@ package com.android.gallery3d.glrenderer; import android.graphics.Bitmap; -import android.graphics.Rect; import android.graphics.RectF; // @@ -40,36 +39,14 @@ public interface GLCanvas { // Clear the drawing buffers. This should only be used by GLRoot. public abstract void clearBuffer(); - public abstract void clearBuffer(float[] argb); - - // Sets and gets the current alpha, alpha must be in [0, 1]. - public abstract void setAlpha(float alpha); - - public abstract float getAlpha(); - - // (current alpha) = (current alpha) * alpha - public abstract void multiplyAlpha(float alpha); - - // Change the current transform matrix. - public abstract void translate(float x, float y, float z); - public abstract void translate(float x, float y); - public abstract void scale(float sx, float sy, float sz); - public abstract void rotate(float angle, float x, float y, float z); - public abstract void multiplyMatrix(float[] mMatrix, int offset); - - // Pushes the configuration state (matrix, and alpha) onto - // a private stack. - public abstract void save(); - // Same as save(), but only save those specified in saveFlags. public abstract void save(int saveFlags); public static final int SAVE_FLAG_ALL = 0xFFFFFFFF; - public static final int SAVE_FLAG_ALPHA = 0x01; public static final int SAVE_FLAG_MATRIX = 0x02; // Pops from the top of the stack as current configuration state (matrix, @@ -78,64 +55,22 @@ public interface GLCanvas { // last save call. public abstract void restore(); - // Draws a line using the specified paint from (x1, y1) to (x2, y2). - // (Both end points are included). - public abstract void drawLine(float x1, float y1, float x2, float y2, GLPaint paint); - - // Draws a rectangle using the specified paint from (x1, y1) to (x2, y2). - // (Both end points are included). - public abstract void drawRect(float x1, float y1, float x2, float y2, GLPaint paint); - - // Fills the specified rectangle with the specified color. - public abstract void fillRect(float x, float y, float width, float height, int color); - // Draws a texture to the specified rectangle. - public abstract void drawTexture( - BasicTexture texture, int x, int y, int width, int height); - - public abstract void drawMesh(BasicTexture tex, int x, int y, int xyBuffer, - int uvBuffer, int indexBuffer, int indexCount); + public abstract void drawTexture(BasicTexture texture, int x, int y, int width, int height); // Draws the source rectangle part of the texture to the target rectangle. public abstract void drawTexture(BasicTexture texture, RectF source, RectF target); - // Draw a texture with a specified texture transform. - public abstract void drawTexture(BasicTexture texture, float[] mTextureTransform, - int x, int y, int w, int h); - - // Draw two textures to the specified rectangle. The actual texture used is - // from * (1 - ratio) + to * ratio - // The two textures must have the same size. - public abstract void drawMixed(BasicTexture from, int toColor, - float ratio, int x, int y, int w, int h); - - // Draw a region of a texture and a specified color to the specified - // rectangle. The actual color used is from * (1 - ratio) + to * ratio. - // The region of the texture is defined by parameter "src". The target - // rectangle is specified by parameter "target". - public abstract void drawMixed(BasicTexture from, int toColor, - float ratio, RectF src, RectF target); - // Unloads the specified texture from the canvas. The resource allocated // to draw the texture will be released. The specified texture will return // to the unloaded state. This function should be called only from // BasicTexture or its descendant public abstract boolean unloadTexture(BasicTexture texture); - // Delete the specified buffer object, similar to unloadTexture. - public abstract void deleteBuffer(int bufferId); - // Delete the textures and buffers in GL side. This function should only be // called in the GL thread. public abstract void deleteRecycledResources(); - // Dump statistics information and clear the counters. For debug only. - public abstract void dumpStatisticsAndClear(); - - public abstract void beginRenderTarget(RawTexture texture); - - public abstract void endRenderTarget(); - /** * Sets texture parameters to use GL_CLAMP_TO_EDGE for both * GL_TEXTURE_WRAP_S and GL_TEXTURE_WRAP_T. Sets texture parameters to be @@ -185,31 +120,4 @@ public interface GLCanvas { * @return The buffer ID that was generated. */ public abstract int uploadBuffer(java.nio.FloatBuffer buffer); - - /** - * Generates buffers and uploads the element array buffer data. - * - * @param buffer The buffer to upload - * @return The buffer ID that was generated. - */ - public abstract int uploadBuffer(java.nio.ByteBuffer buffer); - - /** - * After LightCycle makes GL calls, this method is called to restore the GL - * configuration to the one expected by GLCanvas. - */ - public abstract void recoverFromLightCycle(); - - /** - * Gets the bounds given by x, y, width, and height as well as the internal - * matrix state. There is no special handling for non-90-degree rotations. - * It only considers the lower-left and upper-right corners as the bounds. - * - * @param bounds The output bounds to write to. - * @param x The left side of the input rectangle. - * @param y The bottom of the input rectangle. - * @param width The width of the input rectangle. - * @param height The height of the input rectangle. - */ - public abstract void getBounds(Rect bounds, int x, int y, int width, int height); } diff --git a/WallpaperPicker/src/com/android/gallery3d/glrenderer/GLES20Canvas.java b/WallpaperPicker/src/com/android/gallery3d/glrenderer/GLES20Canvas.java index 933260b48..0da3bae96 100644 --- a/WallpaperPicker/src/com/android/gallery3d/glrenderer/GLES20Canvas.java +++ b/WallpaperPicker/src/com/android/gallery3d/glrenderer/GLES20Canvas.java @@ -16,7 +16,6 @@ package com.android.gallery3d.glrenderer; import android.graphics.Bitmap; -import android.graphics.Rect; import android.graphics.RectF; import android.opengl.GLES20; import android.opengl.GLUtils; @@ -27,24 +26,22 @@ import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; -import java.util.ArrayList; import java.util.Arrays; +import javax.microedition.khronos.opengles.GL11; + public class GLES20Canvas implements GLCanvas { // ************** Constants ********************** private static final String TAG = GLES20Canvas.class.getSimpleName(); private static final int FLOAT_SIZE = Float.SIZE / Byte.SIZE; - private static final float OPAQUE_ALPHA = 0.95f; private static final int COORDS_PER_VERTEX = 2; private static final int VERTEX_STRIDE = COORDS_PER_VERTEX * FLOAT_SIZE; private static final int COUNT_FILL_VERTEX = 4; - private static final int COUNT_LINE_VERTEX = 2; - private static final int COUNT_RECT_VERTEX = 4; private static final int OFFSET_FILL_RECT = 0; - private static final int OFFSET_DRAW_LINE = OFFSET_FILL_RECT + COUNT_FILL_VERTEX; - private static final int OFFSET_DRAW_RECT = OFFSET_DRAW_LINE + COUNT_LINE_VERTEX; + + private static final int GL_TARGET = GL11.GL_TEXTURE_2D; private static final float[] BOX_COORDINATES = { 0, 0, // Fill rectangle @@ -59,33 +56,11 @@ public class GLES20Canvas implements GLCanvas { 1, 0, }; - private static final float[] BOUNDS_COORDINATES = { - 0, 0, 0, 1, - 1, 1, 0, 1, - }; - private static final String POSITION_ATTRIBUTE = "aPosition"; - private static final String COLOR_UNIFORM = "uColor"; private static final String MATRIX_UNIFORM = "uMatrix"; private static final String TEXTURE_MATRIX_UNIFORM = "uTextureMatrix"; private static final String TEXTURE_SAMPLER_UNIFORM = "uTextureSampler"; private static final String ALPHA_UNIFORM = "uAlpha"; - private static final String TEXTURE_COORD_ATTRIBUTE = "aTextureCoordinate"; - - private static final String DRAW_VERTEX_SHADER = "" - + "uniform mat4 " + MATRIX_UNIFORM + ";\n" - + "attribute vec2 " + POSITION_ATTRIBUTE + ";\n" - + "void main() {\n" - + " vec4 pos = vec4(" + POSITION_ATTRIBUTE + ", 0.0, 1.0);\n" - + " gl_Position = " + MATRIX_UNIFORM + " * pos;\n" - + "}\n"; - - private static final String DRAW_FRAGMENT_SHADER = "" - + "precision mediump float;\n" - + "uniform vec4 " + COLOR_UNIFORM + ";\n" - + "void main() {\n" - + " gl_FragColor = " + COLOR_UNIFORM + ";\n" - + "}\n"; private static final String TEXTURE_VERTEX_SHADER = "" + "uniform mat4 " + MATRIX_UNIFORM + ";\n" @@ -98,17 +73,6 @@ public class GLES20Canvas implements GLCanvas { + " vTextureCoord = (" + TEXTURE_MATRIX_UNIFORM + " * pos).xy;\n" + "}\n"; - private static final String MESH_VERTEX_SHADER = "" - + "uniform mat4 " + MATRIX_UNIFORM + ";\n" - + "attribute vec2 " + POSITION_ATTRIBUTE + ";\n" - + "attribute vec2 " + TEXTURE_COORD_ATTRIBUTE + ";\n" - + "varying vec2 vTextureCoord;\n" - + "void main() {\n" - + " vec4 pos = vec4(" + POSITION_ATTRIBUTE + ", 0.0, 1.0);\n" - + " gl_Position = " + MATRIX_UNIFORM + " * pos;\n" - + " vTextureCoord = " + TEXTURE_COORD_ATTRIBUTE + ";\n" - + "}\n"; - private static final String TEXTURE_FRAGMENT_SHADER = "" + "precision mediump float;\n" + "varying vec2 vTextureCoord;\n" @@ -119,26 +83,13 @@ public class GLES20Canvas implements GLCanvas { + " gl_FragColor *= " + ALPHA_UNIFORM + ";\n" + "}\n"; - private static final String OES_TEXTURE_FRAGMENT_SHADER = "" - + "#extension GL_OES_EGL_image_external : require\n" - + "precision mediump float;\n" - + "varying vec2 vTextureCoord;\n" - + "uniform float " + ALPHA_UNIFORM + ";\n" - + "uniform samplerExternalOES " + TEXTURE_SAMPLER_UNIFORM + ";\n" - + "void main() {\n" - + " gl_FragColor = texture2D(" + TEXTURE_SAMPLER_UNIFORM + ", vTextureCoord);\n" - + " gl_FragColor *= " + ALPHA_UNIFORM + ";\n" - + "}\n"; - private static final int INITIAL_RESTORE_STATE_SIZE = 8; private static final int MATRIX_SIZE = 16; // Keep track of restore state private float[] mMatrices = new float[INITIAL_RESTORE_STATE_SIZE * MATRIX_SIZE]; - private float[] mAlphas = new float[INITIAL_RESTORE_STATE_SIZE]; private IntArray mSaveFlags = new IntArray(); - private int mCurrentAlphaIndex = 0; private int mCurrentMatrixIndex = 0; // Viewport size @@ -148,15 +99,8 @@ public class GLES20Canvas implements GLCanvas { // Projection matrix private float[] mProjectionMatrix = new float[MATRIX_SIZE]; - // Screen size for when we aren't bound to a texture - private int mScreenWidth; - private int mScreenHeight; - // GL programs - private int mDrawProgram; private int mTextureProgram; - private int mOesTextureProgram; - private int mMeshProgram; // GL buffer containing BOX_COORDINATES private int mBoxCoordinates; @@ -165,17 +109,11 @@ public class GLES20Canvas implements GLCanvas { private static final int INDEX_POSITION = 0; private static final int INDEX_MATRIX = 1; - // Handle indices -- draw - private static final int INDEX_COLOR = 2; - // Handle indices -- texture private static final int INDEX_TEXTURE_MATRIX = 2; private static final int INDEX_TEXTURE_SAMPLER = 3; private static final int INDEX_ALPHA = 4; - // Handle indices -- mesh - private static final int INDEX_TEXTURE_COORD = 2; - private abstract static class ShaderParameter { public int handle; protected final String mName; @@ -211,52 +149,18 @@ public class GLES20Canvas implements GLCanvas { } } - ShaderParameter[] mDrawParameters = { - new AttributeShaderParameter(POSITION_ATTRIBUTE), // INDEX_POSITION - new UniformShaderParameter(MATRIX_UNIFORM), // INDEX_MATRIX - new UniformShaderParameter(COLOR_UNIFORM), // INDEX_COLOR - }; - ShaderParameter[] mTextureParameters = { + private ShaderParameter[] mTextureParameters = { new AttributeShaderParameter(POSITION_ATTRIBUTE), // INDEX_POSITION new UniformShaderParameter(MATRIX_UNIFORM), // INDEX_MATRIX new UniformShaderParameter(TEXTURE_MATRIX_UNIFORM), // INDEX_TEXTURE_MATRIX new UniformShaderParameter(TEXTURE_SAMPLER_UNIFORM), // INDEX_TEXTURE_SAMPLER new UniformShaderParameter(ALPHA_UNIFORM), // INDEX_ALPHA }; - ShaderParameter[] mOesTextureParameters = { - new AttributeShaderParameter(POSITION_ATTRIBUTE), // INDEX_POSITION - new UniformShaderParameter(MATRIX_UNIFORM), // INDEX_MATRIX - new UniformShaderParameter(TEXTURE_MATRIX_UNIFORM), // INDEX_TEXTURE_MATRIX - new UniformShaderParameter(TEXTURE_SAMPLER_UNIFORM), // INDEX_TEXTURE_SAMPLER - new UniformShaderParameter(ALPHA_UNIFORM), // INDEX_ALPHA - }; - ShaderParameter[] mMeshParameters = { - new AttributeShaderParameter(POSITION_ATTRIBUTE), // INDEX_POSITION - new UniformShaderParameter(MATRIX_UNIFORM), // INDEX_MATRIX - new AttributeShaderParameter(TEXTURE_COORD_ATTRIBUTE), // INDEX_TEXTURE_COORD - new UniformShaderParameter(TEXTURE_SAMPLER_UNIFORM), // INDEX_TEXTURE_SAMPLER - new UniformShaderParameter(ALPHA_UNIFORM), // INDEX_ALPHA - }; private final IntArray mUnboundTextures = new IntArray(); - private final IntArray mDeleteBuffers = new IntArray(); - - // Keep track of statistics for debugging - private int mCountDrawMesh = 0; - private int mCountTextureRect = 0; - private int mCountFillRect = 0; - private int mCountDrawLine = 0; - - // Buffer for framebuffer IDs -- we keep track so we can switch the attached - // texture. - private int[] mFrameBuffer = new int[1]; - - // Bound textures. - private ArrayList<RawTexture> mTargetTextures = new ArrayList<RawTexture>(); // Temporary variables used within calculations private final float[] mTempMatrix = new float[32]; - private final float[] mTempColor = new float[4]; private final RectF mTempSourceRect = new RectF(); private final RectF mTempTargetRect = new RectF(); private final float[] mTempTextureMatrix = new float[MATRIX_SIZE]; @@ -267,26 +171,15 @@ public class GLES20Canvas implements GLCanvas { public GLES20Canvas() { Matrix.setIdentityM(mTempTextureMatrix, 0); Matrix.setIdentityM(mMatrices, mCurrentMatrixIndex); - mAlphas[mCurrentAlphaIndex] = 1f; - mTargetTextures.add(null); FloatBuffer boxBuffer = createBuffer(BOX_COORDINATES); mBoxCoordinates = uploadBuffer(boxBuffer); - int drawVertexShader = loadShader(GLES20.GL_VERTEX_SHADER, DRAW_VERTEX_SHADER); int textureVertexShader = loadShader(GLES20.GL_VERTEX_SHADER, TEXTURE_VERTEX_SHADER); - int meshVertexShader = loadShader(GLES20.GL_VERTEX_SHADER, MESH_VERTEX_SHADER); - int drawFragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, DRAW_FRAGMENT_SHADER); int textureFragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, TEXTURE_FRAGMENT_SHADER); - int oesTextureFragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, - OES_TEXTURE_FRAGMENT_SHADER); - mDrawProgram = assembleProgram(drawVertexShader, drawFragmentShader, mDrawParameters); mTextureProgram = assembleProgram(textureVertexShader, textureFragmentShader, mTextureParameters); - mOesTextureProgram = assembleProgram(textureVertexShader, oesTextureFragmentShader, - mOesTextureParameters); - mMeshProgram = assembleProgram(meshVertexShader, textureFragmentShader, mMeshParameters); GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA); checkError(); } @@ -348,12 +241,8 @@ public class GLES20Canvas implements GLCanvas { checkError(); Matrix.setIdentityM(mMatrices, mCurrentMatrixIndex); Matrix.orthoM(mProjectionMatrix, 0, 0, width, 0, height, -1, 1); - if (getTargetTexture() == null) { - mScreenWidth = width; - mScreenHeight = height; - Matrix.translateM(mMatrices, mCurrentMatrixIndex, 0, height, 0); - Matrix.scaleM(mMatrices, mCurrentMatrixIndex, 1, -1, 1); - } + Matrix.translateM(mMatrices, mCurrentMatrixIndex, 0, height, 0); + Matrix.scaleM(mMatrices, mCurrentMatrixIndex, 1, -1, 1); } @Override @@ -364,34 +253,6 @@ public class GLES20Canvas implements GLCanvas { checkError(); } - @Override - public void clearBuffer(float[] argb) { - GLES20.glClearColor(argb[1], argb[2], argb[3], argb[0]); - checkError(); - GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); - checkError(); - } - - @Override - public float getAlpha() { - return mAlphas[mCurrentAlphaIndex]; - } - - @Override - public void setAlpha(float alpha) { - mAlphas[mCurrentAlphaIndex] = alpha; - } - - @Override - public void multiplyAlpha(float alpha) { - setAlpha(getAlpha() * alpha); - } - - @Override - public void translate(float x, float y, float z) { - Matrix.translateM(mMatrices, mCurrentMatrixIndex, x, y, z); - } - // This is a faster version of translate(x, y, z) because // (1) we knows z = 0, (2) we inline the Matrix.translateM call, // (3) we unroll the loop @@ -406,11 +267,6 @@ public class GLES20Canvas implements GLCanvas { } @Override - public void scale(float sx, float sy, float sz) { - Matrix.scaleM(mMatrices, mCurrentMatrixIndex, sx, sy, sz); - } - - @Override public void rotate(float angle, float x, float y, float z) { if (angle == 0f) { return; @@ -424,30 +280,7 @@ public class GLES20Canvas implements GLCanvas { } @Override - public void multiplyMatrix(float[] matrix, int offset) { - float[] temp = mTempMatrix; - float[] currentMatrix = mMatrices; - int index = mCurrentMatrixIndex; - Matrix.multiplyMM(temp, 0, currentMatrix, index, matrix, offset); - System.arraycopy(temp, 0, currentMatrix, index, 16); - } - - @Override - public void save() { - save(SAVE_FLAG_ALL); - } - - @Override public void save(int saveFlags) { - boolean saveAlpha = (saveFlags & SAVE_FLAG_ALPHA) == SAVE_FLAG_ALPHA; - if (saveAlpha) { - float currentAlpha = getAlpha(); - mCurrentAlphaIndex++; - if (mAlphas.length <= mCurrentAlphaIndex) { - mAlphas = Arrays.copyOf(mAlphas, mAlphas.length * 2); - } - mAlphas[mCurrentAlphaIndex] = currentAlpha; - } boolean saveMatrix = (saveFlags & SAVE_FLAG_MATRIX) == SAVE_FLAG_MATRIX; if (saveMatrix) { int currentIndex = mCurrentMatrixIndex; @@ -463,82 +296,12 @@ public class GLES20Canvas implements GLCanvas { @Override public void restore() { int restoreFlags = mSaveFlags.removeLast(); - boolean restoreAlpha = (restoreFlags & SAVE_FLAG_ALPHA) == SAVE_FLAG_ALPHA; - if (restoreAlpha) { - mCurrentAlphaIndex--; - } boolean restoreMatrix = (restoreFlags & SAVE_FLAG_MATRIX) == SAVE_FLAG_MATRIX; if (restoreMatrix) { mCurrentMatrixIndex -= MATRIX_SIZE; } } - @Override - public void drawLine(float x1, float y1, float x2, float y2, GLPaint paint) { - draw(GLES20.GL_LINE_STRIP, OFFSET_DRAW_LINE, COUNT_LINE_VERTEX, x1, y1, x2 - x1, y2 - y1, - paint); - mCountDrawLine++; - } - - @Override - public void drawRect(float x, float y, float width, float height, GLPaint paint) { - draw(GLES20.GL_LINE_LOOP, OFFSET_DRAW_RECT, COUNT_RECT_VERTEX, x, y, width, height, paint); - mCountDrawLine++; - } - - private void draw(int type, int offset, int count, float x, float y, float width, float height, - GLPaint paint) { - draw(type, offset, count, x, y, width, height, paint.getColor(), paint.getLineWidth()); - } - - private void draw(int type, int offset, int count, float x, float y, float width, float height, - int color, float lineWidth) { - prepareDraw(offset, color, lineWidth); - draw(mDrawParameters, type, count, x, y, width, height); - } - - private void prepareDraw(int offset, int color, float lineWidth) { - GLES20.glUseProgram(mDrawProgram); - checkError(); - if (lineWidth > 0) { - GLES20.glLineWidth(lineWidth); - checkError(); - } - float[] colorArray = getColor(color); - boolean blendingEnabled = (colorArray[3] < 1f); - enableBlending(blendingEnabled); - if (blendingEnabled) { - GLES20.glBlendColor(colorArray[0], colorArray[1], colorArray[2], colorArray[3]); - checkError(); - } - - GLES20.glUniform4fv(mDrawParameters[INDEX_COLOR].handle, 1, colorArray, 0); - setPosition(mDrawParameters, offset); - checkError(); - } - - private float[] getColor(int color) { - float alpha = ((color >>> 24) & 0xFF) / 255f * getAlpha(); - float red = ((color >>> 16) & 0xFF) / 255f * alpha; - float green = ((color >>> 8) & 0xFF) / 255f * alpha; - float blue = (color & 0xFF) / 255f * alpha; - mTempColor[0] = red; - mTempColor[1] = green; - mTempColor[2] = blue; - mTempColor[3] = alpha; - return mTempColor; - } - - private void enableBlending(boolean enableBlending) { - if (enableBlending) { - GLES20.glEnable(GLES20.GL_BLEND); - checkError(); - } else { - GLES20.glDisable(GLES20.GL_BLEND); - checkError(); - } - } - private void setPosition(ShaderParameter[] params, int offset) { GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mBoxCoordinates); checkError(); @@ -570,13 +333,6 @@ public class GLES20Canvas implements GLCanvas { } @Override - public void fillRect(float x, float y, float width, float height, int color) { - draw(GLES20.GL_TRIANGLE_STRIP, OFFSET_FILL_RECT, COUNT_FILL_VERTEX, x, y, width, height, - color, 0f); - mCountFillRect++; - } - - @Override public void drawTexture(BasicTexture texture, int x, int y, int width, int height) { if (width <= 0 || height <= 0) { return; @@ -588,17 +344,7 @@ public class GLES20Canvas implements GLCanvas { } private static void copyTextureCoordinates(BasicTexture texture, RectF outRect) { - int left = 0; - int top = 0; - int right = texture.getWidth(); - int bottom = texture.getHeight(); - if (texture.hasBorder()) { - left = 1; - top = 1; - right -= 1; - bottom -= 1; - } - outRect.set(left, top, right, bottom); + outRect.set(0, 0, texture.getWidth(), texture.getHeight()); } @Override @@ -613,16 +359,6 @@ public class GLES20Canvas implements GLCanvas { drawTextureRect(texture, mTempSourceRect, mTempTargetRect); } - @Override - public void drawTexture(BasicTexture texture, float[] textureTransform, int x, int y, int w, - int h) { - if (w <= 0 || h <= 0) { - return; - } - mTempTargetRect.set(x, y, x + w, y + h); - drawTextureRect(texture, textureTransform, mTempTargetRect); - } - private void drawTextureRect(BasicTexture texture, RectF source, RectF target) { setTextureMatrix(source); drawTextureRect(texture, mTempTextureMatrix, target); @@ -667,30 +403,15 @@ public class GLES20Canvas implements GLCanvas { setPosition(params, OFFSET_FILL_RECT); GLES20.glUniformMatrix4fv(params[INDEX_TEXTURE_MATRIX].handle, 1, false, textureMatrix, 0); checkError(); - if (texture.isFlippedVertically()) { - save(SAVE_FLAG_MATRIX); - translate(0, target.centerY()); - scale(1, -1, 1); - translate(0, -target.centerY()); - } draw(params, GLES20.GL_TRIANGLE_STRIP, COUNT_FILL_VERTEX, target.left, target.top, target.width(), target.height()); - if (texture.isFlippedVertically()) { - restore(); - } - mCountTextureRect++; } private ShaderParameter[] prepareTexture(BasicTexture texture) { ShaderParameter[] params; int program; - if (texture.getTarget() == GLES20.GL_TEXTURE_2D) { - params = mTextureParameters; - program = mTextureProgram; - } else { - params = mOesTextureParameters; - program = mOesTextureProgram; - } + params = mTextureParameters; + program = mTextureProgram; prepareTexture(texture, program, params); return params; } @@ -699,86 +420,17 @@ public class GLES20Canvas implements GLCanvas { deleteRecycledResources(); GLES20.glUseProgram(program); checkError(); - enableBlending(!texture.isOpaque() || getAlpha() < OPAQUE_ALPHA); + GLES20.glDisable(GLES20.GL_BLEND); + checkError(); GLES20.glActiveTexture(GLES20.GL_TEXTURE0); checkError(); texture.onBind(this); - GLES20.glBindTexture(texture.getTarget(), texture.getId()); + GLES20.glBindTexture(GL_TARGET, texture.getId()); checkError(); GLES20.glUniform1i(params[INDEX_TEXTURE_SAMPLER].handle, 0); checkError(); - GLES20.glUniform1f(params[INDEX_ALPHA].handle, getAlpha()); - checkError(); - } - - @Override - public void drawMesh(BasicTexture texture, int x, int y, int xyBuffer, int uvBuffer, - int indexBuffer, int indexCount) { - prepareTexture(texture, mMeshProgram, mMeshParameters); - - GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, indexBuffer); - checkError(); - - GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, xyBuffer); - checkError(); - int positionHandle = mMeshParameters[INDEX_POSITION].handle; - GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, - VERTEX_STRIDE, 0); - checkError(); - - GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, uvBuffer); - checkError(); - int texCoordHandle = mMeshParameters[INDEX_TEXTURE_COORD].handle; - GLES20.glVertexAttribPointer(texCoordHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, - false, VERTEX_STRIDE, 0); - checkError(); - GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0); - checkError(); - - GLES20.glEnableVertexAttribArray(positionHandle); - checkError(); - GLES20.glEnableVertexAttribArray(texCoordHandle); - checkError(); - - setMatrix(mMeshParameters, x, y, 1, 1); - GLES20.glDrawElements(GLES20.GL_TRIANGLE_STRIP, indexCount, GLES20.GL_UNSIGNED_BYTE, 0); + GLES20.glUniform1f(params[INDEX_ALPHA].handle, 1); checkError(); - - GLES20.glDisableVertexAttribArray(positionHandle); - checkError(); - GLES20.glDisableVertexAttribArray(texCoordHandle); - checkError(); - GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0); - checkError(); - mCountDrawMesh++; - } - - @Override - public void drawMixed(BasicTexture texture, int toColor, float ratio, int x, int y, int w, int h) { - copyTextureCoordinates(texture, mTempSourceRect); - mTempTargetRect.set(x, y, x + w, y + h); - drawMixed(texture, toColor, ratio, mTempSourceRect, mTempTargetRect); - } - - @Override - public void drawMixed(BasicTexture texture, int toColor, float ratio, RectF source, RectF target) { - if (target.width() <= 0 || target.height() <= 0) { - return; - } - save(SAVE_FLAG_ALPHA); - - float currentAlpha = getAlpha(); - float cappedRatio = Math.min(1f, Math.max(0f, ratio)); - - float textureAlpha = (1f - cappedRatio) * currentAlpha; - setAlpha(textureAlpha); - drawTexture(texture, source, target); - - float colorAlpha = cappedRatio * currentAlpha; - setAlpha(colorAlpha); - fillRect(target.left, target.top, target.width(), target.height(), toColor); - - restore(); } @Override @@ -793,13 +445,6 @@ public class GLES20Canvas implements GLCanvas { } @Override - public void deleteBuffer(int bufferId) { - synchronized (mUnboundTextures) { - mDeleteBuffers.add(bufferId); - } - } - - @Override public void deleteRecycledResources() { synchronized (mUnboundTextures) { IntArray ids = mUnboundTextures; @@ -807,134 +452,41 @@ public class GLES20Canvas implements GLCanvas { mGLId.glDeleteTextures(null, ids.size(), ids.getInternalArray(), 0); ids.clear(); } - - ids = mDeleteBuffers; - if (ids.size() > 0) { - mGLId.glDeleteBuffers(null, ids.size(), ids.getInternalArray(), 0); - ids.clear(); - } - } - } - - @Override - public void dumpStatisticsAndClear() { - String line = String.format("MESH:%d, TEX_RECT:%d, FILL_RECT:%d, LINE:%d", mCountDrawMesh, - mCountTextureRect, mCountFillRect, mCountDrawLine); - mCountDrawMesh = 0; - mCountTextureRect = 0; - mCountFillRect = 0; - mCountDrawLine = 0; - Log.d(TAG, line); - } - - @Override - public void endRenderTarget() { - RawTexture oldTexture = mTargetTextures.remove(mTargetTextures.size() - 1); - RawTexture texture = getTargetTexture(); - setRenderTarget(oldTexture, texture); - restore(); // restore matrix and alpha - } - - @Override - public void beginRenderTarget(RawTexture texture) { - save(); // save matrix and alpha and blending - RawTexture oldTexture = getTargetTexture(); - mTargetTextures.add(texture); - setRenderTarget(oldTexture, texture); - } - - private RawTexture getTargetTexture() { - return mTargetTextures.get(mTargetTextures.size() - 1); - } - - private void setRenderTarget(BasicTexture oldTexture, RawTexture texture) { - if (oldTexture == null && texture != null) { - GLES20.glGenFramebuffers(1, mFrameBuffer, 0); - checkError(); - GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffer[0]); - checkError(); - } else if (oldTexture != null && texture == null) { - GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); - checkError(); - GLES20.glDeleteFramebuffers(1, mFrameBuffer, 0); - checkError(); - } - - if (texture == null) { - setSize(mScreenWidth, mScreenHeight); - } else { - setSize(texture.getWidth(), texture.getHeight()); - - if (!texture.isLoaded()) { - texture.prepare(this); - } - - GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, - texture.getTarget(), texture.getId(), 0); - checkError(); - - checkFramebufferStatus(); - } - } - - private static void checkFramebufferStatus() { - int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER); - if (status != GLES20.GL_FRAMEBUFFER_COMPLETE) { - String msg = ""; - switch (status) { - case GLES20.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: - msg = "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT"; - break; - case GLES20.GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: - msg = "GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS"; - break; - case GLES20.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: - msg = "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT"; - break; - case GLES20.GL_FRAMEBUFFER_UNSUPPORTED: - msg = "GL_FRAMEBUFFER_UNSUPPORTED"; - break; - } - throw new RuntimeException(msg + ":" + Integer.toHexString(status)); } } @Override public void setTextureParameters(BasicTexture texture) { - int target = texture.getTarget(); - GLES20.glBindTexture(target, texture.getId()); + GLES20.glBindTexture(GL_TARGET, texture.getId()); checkError(); - GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); - GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); - GLES20.glTexParameterf(target, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); - GLES20.glTexParameterf(target, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); + GLES20.glTexParameteri(GL_TARGET, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); + GLES20.glTexParameteri(GL_TARGET, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); + GLES20.glTexParameterf(GL_TARGET, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); + GLES20.glTexParameterf(GL_TARGET, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); } @Override public void initializeTextureSize(BasicTexture texture, int format, int type) { - int target = texture.getTarget(); - GLES20.glBindTexture(target, texture.getId()); + GLES20.glBindTexture(GL_TARGET, texture.getId()); checkError(); int width = texture.getTextureWidth(); int height = texture.getTextureHeight(); - GLES20.glTexImage2D(target, 0, format, width, height, 0, format, type, null); + GLES20.glTexImage2D(GL_TARGET, 0, format, width, height, 0, format, type, null); } @Override public void initializeTexture(BasicTexture texture, Bitmap bitmap) { - int target = texture.getTarget(); - GLES20.glBindTexture(target, texture.getId()); + GLES20.glBindTexture(GL_TARGET, texture.getId()); checkError(); - GLUtils.texImage2D(target, 0, bitmap, 0); + GLUtils.texImage2D(GL_TARGET, 0, bitmap, 0); } @Override public void texSubImage2D(BasicTexture texture, int xOffset, int yOffset, Bitmap bitmap, int format, int type) { - int target = texture.getTarget(); - GLES20.glBindTexture(target, texture.getId()); + GLES20.glBindTexture(GL_TARGET, texture.getId()); checkError(); - GLUtils.texSubImage2D(target, 0, xOffset, yOffset, bitmap, format, type); + GLUtils.texSubImage2D(GL_TARGET, 0, xOffset, yOffset, bitmap, format, type); } @Override @@ -942,11 +494,6 @@ public class GLES20Canvas implements GLCanvas { return uploadBuffer(buf, FLOAT_SIZE); } - @Override - public int uploadBuffer(ByteBuffer buf) { - return uploadBuffer(buf, 1); - } - private int uploadBuffer(Buffer buffer, int elementSize) { mGLId.glGenBuffers(1, mTempIntArray, 0); checkError(); @@ -967,40 +514,6 @@ public class GLES20Canvas implements GLCanvas { } } - @SuppressWarnings("unused") - private static void printMatrix(String message, float[] m, int offset) { - StringBuilder b = new StringBuilder(message); - for (int i = 0; i < MATRIX_SIZE; i++) { - b.append(' '); - if (i % 4 == 0) { - b.append('\n'); - } - b.append(m[offset + i]); - } - Log.v(TAG, b.toString()); - } - - @Override - public void recoverFromLightCycle() { - GLES20.glViewport(0, 0, mWidth, mHeight); - GLES20.glDisable(GLES20.GL_DEPTH_TEST); - GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA); - checkError(); - } - - @Override - public void getBounds(Rect bounds, int x, int y, int width, int height) { - Matrix.translateM(mTempMatrix, 0, mMatrices, mCurrentMatrixIndex, x, y, 0f); - Matrix.scaleM(mTempMatrix, 0, width, height, 1f); - Matrix.multiplyMV(mTempMatrix, MATRIX_SIZE, mTempMatrix, 0, BOUNDS_COORDINATES, 0); - Matrix.multiplyMV(mTempMatrix, MATRIX_SIZE + 4, mTempMatrix, 0, BOUNDS_COORDINATES, 4); - bounds.left = Math.round(mTempMatrix[MATRIX_SIZE]); - bounds.right = Math.round(mTempMatrix[MATRIX_SIZE + 4]); - bounds.top = Math.round(mTempMatrix[MATRIX_SIZE + 1]); - bounds.bottom = Math.round(mTempMatrix[MATRIX_SIZE + 5]); - bounds.sort(); - } - @Override public GLId getGLId() { return mGLId; diff --git a/WallpaperPicker/src/com/android/gallery3d/glrenderer/GLPaint.java b/WallpaperPicker/src/com/android/gallery3d/glrenderer/GLPaint.java deleted file mode 100644 index b26e9ab29..000000000 --- a/WallpaperPicker/src/com/android/gallery3d/glrenderer/GLPaint.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2010 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 com.android.gallery3d.glrenderer; - -import com.android.gallery3d.common.Utils; - -public class GLPaint { - private float mLineWidth = 1f; - private int mColor = 0; - - public void setColor(int color) { - mColor = color; - } - - public int getColor() { - return mColor; - } - - public void setLineWidth(float width) { - Utils.assertTrue(width >= 0); - mLineWidth = width; - } - - public float getLineWidth() { - return mLineWidth; - } -} diff --git a/WallpaperPicker/src/com/android/gallery3d/glrenderer/RawTexture.java b/WallpaperPicker/src/com/android/gallery3d/glrenderer/RawTexture.java deleted file mode 100644 index 93f0fdff9..000000000 --- a/WallpaperPicker/src/com/android/gallery3d/glrenderer/RawTexture.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2010 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 com.android.gallery3d.glrenderer; - -import android.util.Log; - -import javax.microedition.khronos.opengles.GL11; - -public class RawTexture extends BasicTexture { - private static final String TAG = "RawTexture"; - - private final boolean mOpaque; - private boolean mIsFlipped; - - public RawTexture(int width, int height, boolean opaque) { - mOpaque = opaque; - setSize(width, height); - } - - @Override - public boolean isOpaque() { - return mOpaque; - } - - @Override - public boolean isFlippedVertically() { - return mIsFlipped; - } - - public void setIsFlippedVertically(boolean isFlipped) { - mIsFlipped = isFlipped; - } - - protected void prepare(GLCanvas canvas) { - GLId glId = canvas.getGLId(); - mId = glId.generateTexture(); - canvas.initializeTextureSize(this, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE); - canvas.setTextureParameters(this); - mState = STATE_LOADED; - setAssociatedCanvas(canvas); - } - - @Override - protected boolean onBind(GLCanvas canvas) { - if (isLoaded()) return true; - Log.w(TAG, "lost the content due to context change"); - return false; - } - - @Override - public void yield() { - // we cannot free the texture because we have no backup. - } - - @Override - protected int getTarget() { - return GL11.GL_TEXTURE_2D; - } -} diff --git a/WallpaperPicker/src/com/android/gallery3d/glrenderer/Texture.java b/WallpaperPicker/src/com/android/gallery3d/glrenderer/Texture.java index 3dcae4aec..e71a379bc 100644 --- a/WallpaperPicker/src/com/android/gallery3d/glrenderer/Texture.java +++ b/WallpaperPicker/src/com/android/gallery3d/glrenderer/Texture.java @@ -24,21 +24,14 @@ package com.android.gallery3d.glrenderer; // This is the current texture hierarchy: // // Texture -// -- ColorTexture -// -- FadeInTexture // -- BasicTexture // -- UploadedTexture // -- BitmapTexture // -- Tile -// -- ResourceTexture -// -- NinePatchTexture -// -- CanvasTexture -// -- StringTexture // public interface Texture { public int getWidth(); public int getHeight(); public void draw(GLCanvas canvas, int x, int y); public void draw(GLCanvas canvas, int x, int y, int w, int h); - public boolean isOpaque(); } diff --git a/WallpaperPicker/src/com/android/gallery3d/glrenderer/UploadedTexture.java b/WallpaperPicker/src/com/android/gallery3d/glrenderer/UploadedTexture.java index 8075bf868..607e2a943 100644 --- a/WallpaperPicker/src/com/android/gallery3d/glrenderer/UploadedTexture.java +++ b/WallpaperPicker/src/com/android/gallery3d/glrenderer/UploadedTexture.java @@ -19,14 +19,12 @@ package com.android.gallery3d.glrenderer; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.opengl.GLUtils; +import android.util.Pair; import com.android.gallery3d.common.Utils; -import com.android.launcher3.util.Thunk; import java.util.HashMap; -import javax.microedition.khronos.opengles.GL11; - // UploadedTextures use a Bitmap for the content of the texture. // // Subclasses should implement onGetBitmap() to provide the Bitmap and @@ -45,89 +43,29 @@ public abstract class UploadedTexture extends BasicTexture { // To prevent keeping allocation the borders, we store those used borders here. // Since the length will be power of two, it won't use too much memory. - private static HashMap<BorderKey, Bitmap> sBorderLines = - new HashMap<BorderKey, Bitmap>(); - private static BorderKey sBorderKey = new BorderKey(); - - @SuppressWarnings("unused") - private static final String TAG = "Texture"; - private boolean mContentValid = true; + private static HashMap<BorderKey, Bitmap> sBorderLines = new HashMap<BorderKey, Bitmap>(); - // indicate this textures is being uploaded in background - private boolean mIsUploading = false; - private boolean mOpaque = true; - private boolean mThrottled = false; - private static int sUploadedCount; - private static final int UPLOAD_LIMIT = 100; + private static class BorderKey extends Pair<Config, Integer> { + public BorderKey(Config config, boolean vertical, int length) { + super(config, vertical ? length : -length); + } + } + private boolean mContentValid = true; protected Bitmap mBitmap; - private int mBorder; protected UploadedTexture() { - this(false); - } - - protected UploadedTexture(boolean hasBorder) { super(null, 0, STATE_UNLOADED); - if (hasBorder) { - setBorder(true); - mBorder = 1; - } - } - - protected void setIsUploading(boolean uploading) { - mIsUploading = uploading; } - public boolean isUploading() { - return mIsUploading; - } - - @Thunk static class BorderKey implements Cloneable { - public boolean vertical; - public Config config; - public int length; - - @Override - public int hashCode() { - int x = config.hashCode() ^ length; - return vertical ? x : -x; - } - - @Override - public boolean equals(Object object) { - if (!(object instanceof BorderKey)) return false; - BorderKey o = (BorderKey) object; - return vertical == o.vertical - && config == o.config && length == o.length; - } - - @Override - public BorderKey clone() { - try { - return (BorderKey) super.clone(); - } catch (CloneNotSupportedException e) { - throw new AssertionError(e); - } - } - } - - protected void setThrottled(boolean throttled) { - mThrottled = throttled; - } - - private static Bitmap getBorderLine( - boolean vertical, Config config, int length) { - BorderKey key = sBorderKey; - key.vertical = vertical; - key.config = config; - key.length = length; + private static Bitmap getBorderLine(boolean vertical, Config config, int length) { + BorderKey key = new BorderKey(config, vertical, length); Bitmap bitmap = sBorderLines.get(key); if (bitmap == null) { bitmap = vertical ? Bitmap.createBitmap(1, length, config) : Bitmap.createBitmap(length, 1, config); - sBorderLines.put(key.clone(), bitmap); + sBorderLines.put(key, bitmap); } return bitmap; } @@ -135,8 +73,8 @@ public abstract class UploadedTexture extends BasicTexture { private Bitmap getBitmap() { if (mBitmap == null) { mBitmap = onGetBitmap(); - int w = mBitmap.getWidth() + mBorder * 2; - int h = mBitmap.getHeight() + mBorder * 2; + int w = mBitmap.getWidth(); + int h = mBitmap.getHeight(); if (mWidth == UNSPECIFIED) { setSize(w, h); } @@ -186,37 +124,23 @@ public abstract class UploadedTexture extends BasicTexture { */ public void updateContent(GLCanvas canvas) { if (!isLoaded()) { - if (mThrottled && ++sUploadedCount > UPLOAD_LIMIT) { - return; - } uploadToCanvas(canvas); } else if (!mContentValid) { Bitmap bitmap = getBitmap(); int format = GLUtils.getInternalFormat(bitmap); int type = GLUtils.getType(bitmap); - canvas.texSubImage2D(this, mBorder, mBorder, bitmap, format, type); + canvas.texSubImage2D(this, 0, 0, bitmap, format, type); freeBitmap(); mContentValid = true; } } - public static void resetUploadLimit() { - sUploadedCount = 0; - } - - public static boolean uploadLimitReached() { - return sUploadedCount > UPLOAD_LIMIT; - } - private void uploadToCanvas(GLCanvas canvas) { - Bitmap bitmap = getBitmap(); if (bitmap != null) { try { int bWidth = bitmap.getWidth(); int bHeight = bitmap.getHeight(); - int width = bWidth + mBorder * 2; - int height = bHeight + mBorder * 2; int texWidth = getTextureWidth(); int texHeight = getTextureHeight(); @@ -234,28 +158,18 @@ public abstract class UploadedTexture extends BasicTexture { Config config = bitmap.getConfig(); canvas.initializeTextureSize(this, format, type); - canvas.texSubImage2D(this, mBorder, mBorder, bitmap, format, type); - - if (mBorder > 0) { - // Left border - Bitmap line = getBorderLine(true, config, texHeight); - canvas.texSubImage2D(this, 0, 0, line, format, type); - - // Top border - line = getBorderLine(false, config, texWidth); - canvas.texSubImage2D(this, 0, 0, line, format, type); - } + canvas.texSubImage2D(this, 0, 0, bitmap, format, type); // Right border - if (mBorder + bWidth < texWidth) { + if (bWidth < texWidth) { Bitmap line = getBorderLine(true, config, texHeight); - canvas.texSubImage2D(this, mBorder + bWidth, 0, line, format, type); + canvas.texSubImage2D(this, bWidth, 0, line, format, type); } // Bottom border - if (mBorder + bHeight < texHeight) { + if (bHeight < texHeight) { Bitmap line = getBorderLine(false, config, texWidth); - canvas.texSubImage2D(this, 0, mBorder + bHeight, line, format, type); + canvas.texSubImage2D(this, 0, bHeight, line, format, type); } } } finally { @@ -278,20 +192,6 @@ public abstract class UploadedTexture extends BasicTexture { } @Override - protected int getTarget() { - return GL11.GL_TEXTURE_2D; - } - - public void setOpaque(boolean isOpaque) { - mOpaque = isOpaque; - } - - @Override - public boolean isOpaque() { - return mOpaque; - } - - @Override public void recycle() { super.recycle(); if (mBitmap != null) freeBitmap(); diff --git a/WallpaperPicker/src/com/android/launcher3/CropView.java b/WallpaperPicker/src/com/android/launcher3/CropView.java index e98e23e98..4770a7177 100644 --- a/WallpaperPicker/src/com/android/launcher3/CropView.java +++ b/WallpaperPicker/src/com/android/launcher3/CropView.java @@ -31,7 +31,7 @@ import android.view.ViewTreeObserver.OnGlobalLayoutListener; import com.android.photos.views.TiledImageRenderer.TileSource; import com.android.photos.views.TiledImageView; -public class CropView extends TiledImageView implements OnScaleGestureListener { +public class CropView extends TiledImageView implements OnScaleGestureListener { private ScaleGestureDetector mScaleGestureDetector; private long mTouchDownTime; diff --git a/WallpaperPicker/src/com/android/launcher3/LiveWallpaperListAdapter.java b/WallpaperPicker/src/com/android/launcher3/LiveWallpaperListAdapter.java deleted file mode 100644 index b53fce119..000000000 --- a/WallpaperPicker/src/com/android/launcher3/LiveWallpaperListAdapter.java +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright (C) 2010 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 com.android.launcher3; - -import android.app.WallpaperInfo; -import android.app.WallpaperManager; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.graphics.drawable.Drawable; -import android.os.AsyncTask; -import android.service.wallpaper.WallpaperService; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.ImageView; -import android.widget.ListAdapter; -import android.widget.TextView; - -import com.android.launcher3.util.Thunk; - -import org.xmlpull.v1.XmlPullParserException; - -import java.io.IOException; -import java.text.Collator; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -public class LiveWallpaperListAdapter extends BaseAdapter implements ListAdapter { - private static final String LOG_TAG = "LiveWallpaperListAdapter"; - - private final LayoutInflater mInflater; - private final PackageManager mPackageManager; - - @Thunk List<LiveWallpaperTile> mWallpapers; - - @SuppressWarnings("unchecked") - public LiveWallpaperListAdapter(Context context) { - mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - mPackageManager = context.getPackageManager(); - - List<ResolveInfo> list = mPackageManager.queryIntentServices( - new Intent(WallpaperService.SERVICE_INTERFACE), - PackageManager.GET_META_DATA); - - mWallpapers = new ArrayList<LiveWallpaperTile>(); - - new LiveWallpaperEnumerator(context).execute(list); - } - - public int getCount() { - if (mWallpapers == null) { - return 0; - } - return mWallpapers.size(); - } - - public LiveWallpaperTile getItem(int position) { - return mWallpapers.get(position); - } - - public long getItemId(int position) { - return position; - } - - public View getView(int position, View convertView, ViewGroup parent) { - View view; - - if (convertView == null) { - view = mInflater.inflate(R.layout.wallpaper_picker_live_wallpaper_item, parent, false); - } else { - view = convertView; - } - - LiveWallpaperTile wallpaperInfo = mWallpapers.get(position); - wallpaperInfo.setView(view); - ImageView image = (ImageView) view.findViewById(R.id.wallpaper_image); - ImageView icon = (ImageView) view.findViewById(R.id.wallpaper_icon); - if (wallpaperInfo.mThumbnail != null) { - image.setImageDrawable(wallpaperInfo.mThumbnail); - icon.setVisibility(View.GONE); - } else { - icon.setImageDrawable(wallpaperInfo.mInfo.loadIcon(mPackageManager)); - icon.setVisibility(View.VISIBLE); - } - - TextView label = (TextView) view.findViewById(R.id.wallpaper_item_label); - label.setText(wallpaperInfo.mInfo.loadLabel(mPackageManager)); - - return view; - } - - public static class LiveWallpaperTile extends WallpaperPickerActivity.WallpaperTileInfo { - @Thunk Drawable mThumbnail; - @Thunk WallpaperInfo mInfo; - public LiveWallpaperTile(Drawable thumbnail, WallpaperInfo info, Intent intent) { - mThumbnail = thumbnail; - mInfo = info; - } - @Override - public void onClick(WallpaperPickerActivity a) { - Intent preview = new Intent(WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER); - preview.putExtra(WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT, - mInfo.getComponent()); - a.startActivityForResultSafely(preview, - WallpaperPickerActivity.PICK_WALLPAPER_THIRD_PARTY_ACTIVITY); - } - } - - private class LiveWallpaperEnumerator extends - AsyncTask<List<ResolveInfo>, LiveWallpaperTile, Void> { - private Context mContext; - private int mWallpaperPosition; - - public LiveWallpaperEnumerator(Context context) { - super(); - mContext = context; - mWallpaperPosition = 0; - } - - @Override - protected Void doInBackground(List<ResolveInfo>... params) { - final PackageManager packageManager = mContext.getPackageManager(); - - List<ResolveInfo> list = params[0]; - - Collections.sort(list, new Comparator<ResolveInfo>() { - final Collator mCollator; - - { - mCollator = Collator.getInstance(); - } - - public int compare(ResolveInfo info1, ResolveInfo info2) { - return mCollator.compare(info1.loadLabel(packageManager), - info2.loadLabel(packageManager)); - } - }); - - for (ResolveInfo resolveInfo : list) { - WallpaperInfo info = null; - try { - info = new WallpaperInfo(mContext, resolveInfo); - } catch (XmlPullParserException e) { - Log.w(LOG_TAG, "Skipping wallpaper " + resolveInfo.serviceInfo, e); - continue; - } catch (IOException e) { - Log.w(LOG_TAG, "Skipping wallpaper " + resolveInfo.serviceInfo, e); - continue; - } - - - Drawable thumb = info.loadThumbnail(packageManager); - Intent launchIntent = new Intent(WallpaperService.SERVICE_INTERFACE); - launchIntent.setClassName(info.getPackageName(), info.getServiceName()); - LiveWallpaperTile wallpaper = new LiveWallpaperTile(thumb, info, launchIntent); - publishProgress(wallpaper); - } - // Send a null object to show loading is finished - publishProgress((LiveWallpaperTile) null); - - return null; - } - - @Override - protected void onProgressUpdate(LiveWallpaperTile...infos) { - for (LiveWallpaperTile info : infos) { - if (info == null) { - LiveWallpaperListAdapter.this.notifyDataSetChanged(); - break; - } - if (info.mThumbnail != null) { - info.mThumbnail.setDither(true); - } - if (mWallpaperPosition < mWallpapers.size()) { - mWallpapers.set(mWallpaperPosition, info); - } else { - mWallpapers.add(info); - } - mWallpaperPosition++; - } - } - } -} diff --git a/WallpaperPicker/src/com/android/launcher3/SavedWallpaperImages.java b/WallpaperPicker/src/com/android/launcher3/SavedWallpaperImages.java index 64b0ac466..9124e414a 100644 --- a/WallpaperPicker/src/com/android/launcher3/SavedWallpaperImages.java +++ b/WallpaperPicker/src/com/android/launcher3/SavedWallpaperImages.java @@ -26,29 +26,24 @@ import android.graphics.BitmapFactory; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.util.Log; -import android.util.Pair; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.ListAdapter; + +import com.android.launcher3.wallpapertileinfo.FileWallpaperInfo; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; +import java.util.List; +public class SavedWallpaperImages { -public class SavedWallpaperImages extends BaseAdapter implements ListAdapter { private static String TAG = "Launcher3.SavedWallpaperImages"; - private ImageDb mDb; - ArrayList<SavedWallpaperTile> mImages; - Context mContext; - LayoutInflater mLayoutInflater; - public static class SavedWallpaperTile extends WallpaperPickerActivity.FileWallpaperInfo { + public static class SavedWallpaperInfo extends FileWallpaperInfo { + private int mDbId; - public SavedWallpaperTile(int dbId, File target, Drawable thumb) { + + public SavedWallpaperInfo(int dbId, File target, Drawable thumb) { super(target, thumb); mDbId = dbId; } @@ -59,19 +54,22 @@ public class SavedWallpaperImages extends BaseAdapter implements ListAdapter { } } + private final ImageDb mDb; + private final Context mContext; + public SavedWallpaperImages(Context context) { // We used to store the saved images in the cache directory, but that meant they'd get // deleted sometimes-- move them to the data directory ImageDb.moveFromCacheDirectoryIfNecessary(context); mDb = new ImageDb(context); mContext = context; - mLayoutInflater = LayoutInflater.from(context); } - public void loadThumbnailsAndImageIdList() { - mImages = new ArrayList<SavedWallpaperTile>(); + public List<SavedWallpaperInfo> loadThumbnailsAndImageIdList() { + List<SavedWallpaperInfo> result = new ArrayList<SavedWallpaperInfo>(); + SQLiteDatabase db = mDb.getReadableDatabase(); - Cursor result = db.query(ImageDb.TABLE_NAME, + Cursor c = db.query(ImageDb.TABLE_NAME, new String[] { ImageDb.COLUMN_ID, ImageDb.COLUMN_IMAGE_THUMBNAIL_FILENAME, ImageDb.COLUMN_IMAGE_FILENAME}, // cols to return @@ -82,43 +80,24 @@ public class SavedWallpaperImages extends BaseAdapter implements ListAdapter { ImageDb.COLUMN_ID + " DESC", null); - while (result.moveToNext()) { - String filename = result.getString(1); + while (c.moveToNext()) { + String filename = c.getString(1); File file = new File(mContext.getFilesDir(), filename); Bitmap thumb = BitmapFactory.decodeFile(file.getAbsolutePath()); if (thumb != null) { - mImages.add(new SavedWallpaperTile(result.getInt(0), - new File(mContext.getFilesDir(), result.getString(2)), - new BitmapDrawable(thumb))); + result.add(new SavedWallpaperInfo(c.getInt(0), + new File(mContext.getFilesDir(), c.getString(2)), + new BitmapDrawable(mContext.getResources(), thumb))); } } - result.close(); - } - - public int getCount() { - return mImages.size(); + c.close(); + return result; } - public SavedWallpaperTile getItem(int position) { - return mImages.get(position); - } - - public long getItemId(int position) { - return position; - } - - public View getView(int position, View convertView, ViewGroup parent) { - Drawable thumbDrawable = mImages.get(position).mThumb; - if (thumbDrawable == null) { - Log.e(TAG, "Error decoding thumbnail for wallpaper #" + position); - } - return WallpaperPickerActivity.createImageTileView( - mLayoutInflater, convertView, parent, thumbDrawable); - } + public void deleteImage(int id) { + SQLiteDatabase db = mDb.getWritableDatabase(); - private Pair<String, String> getImageFilenames(int id) { - SQLiteDatabase db = mDb.getReadableDatabase(); Cursor result = db.query(ImageDb.TABLE_NAME, new String[] { ImageDb.COLUMN_IMAGE_THUMBNAIL_FILENAME, ImageDb.COLUMN_IMAGE_FILENAME }, // cols to return @@ -128,24 +107,12 @@ public class SavedWallpaperImages extends BaseAdapter implements ListAdapter { null, null, null); - if (result.getCount() > 0) { - result.moveToFirst(); - String thumbFilename = result.getString(0); - String imageFilename = result.getString(1); - result.close(); - return new Pair<String, String>(thumbFilename, imageFilename); - } else { - return null; + if (result.moveToFirst()) { + new File(mContext.getFilesDir(), result.getString(0)).delete(); + new File(mContext.getFilesDir(), result.getString(1)).delete(); } - } + result.close(); - public void deleteImage(int id) { - Pair<String, String> filenames = getImageFilenames(id); - File imageFile = new File(mContext.getFilesDir(), filenames.first); - imageFile.delete(); - File thumbFile = new File(mContext.getFilesDir(), filenames.second); - thumbFile.delete(); - SQLiteDatabase db = mDb.getWritableDatabase(); db.delete(ImageDb.TABLE_NAME, ImageDb.COLUMN_ID + " = ?", // SELECT query new String[] { @@ -177,20 +144,16 @@ public class SavedWallpaperImages extends BaseAdapter implements ListAdapter { } } - static class ImageDb extends SQLiteOpenHelper { + private static class ImageDb extends SQLiteOpenHelper { final static int DB_VERSION = 1; final static String TABLE_NAME = "saved_wallpaper_images"; final static String COLUMN_ID = "id"; final static String COLUMN_IMAGE_THUMBNAIL_FILENAME = "image_thumbnail"; final static String COLUMN_IMAGE_FILENAME = "image"; - Context mContext; - public ImageDb(Context context) { super(context, context.getDatabasePath(LauncherFiles.WALLPAPER_IMAGES_DB).getPath(), null, DB_VERSION); - // Store the context for later use - mContext = context; } public static void moveFromCacheDirectoryIfNecessary(Context context) { diff --git a/WallpaperPicker/src/com/android/launcher3/ThirdPartyWallpaperPickerListAdapter.java b/WallpaperPicker/src/com/android/launcher3/ThirdPartyWallpaperPickerListAdapter.java deleted file mode 100644 index 099bbda7b..000000000 --- a/WallpaperPicker/src/com/android/launcher3/ThirdPartyWallpaperPickerListAdapter.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (C) 2010 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 com.android.launcher3; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.ListAdapter; -import android.widget.TextView; - -import com.android.launcher3.util.Thunk; - -import java.util.ArrayList; -import java.util.List; - -public class ThirdPartyWallpaperPickerListAdapter extends BaseAdapter implements ListAdapter { - private final LayoutInflater mInflater; - private final PackageManager mPackageManager; - private final int mIconSize; - - private List<ThirdPartyWallpaperTile> mThirdPartyWallpaperPickers = - new ArrayList<ThirdPartyWallpaperTile>(); - - public static class ThirdPartyWallpaperTile extends WallpaperPickerActivity.WallpaperTileInfo { - @Thunk ResolveInfo mResolveInfo; - public ThirdPartyWallpaperTile(ResolveInfo resolveInfo) { - mResolveInfo = resolveInfo; - } - @Override - public void onClick(WallpaperPickerActivity a) { - final ComponentName itemComponentName = new ComponentName( - mResolveInfo.activityInfo.packageName, mResolveInfo.activityInfo.name); - Intent launchIntent = new Intent(Intent.ACTION_SET_WALLPAPER); - launchIntent.setComponent(itemComponentName) - .putExtra(WallpaperPickerActivity.EXTRA_WALLPAPER_OFFSET, - a.getWallpaperParallaxOffset()); - a.startActivityForResultSafely( - launchIntent, WallpaperPickerActivity.PICK_WALLPAPER_THIRD_PARTY_ACTIVITY); - } - } - - public ThirdPartyWallpaperPickerListAdapter(Context context) { - mInflater = LayoutInflater.from(context); - mPackageManager = context.getPackageManager(); - mIconSize = context.getResources().getDimensionPixelSize(R.dimen.wallpaperItemIconSize); - final PackageManager pm = mPackageManager; - - final Intent pickWallpaperIntent = new Intent(Intent.ACTION_SET_WALLPAPER); - final List<ResolveInfo> apps = - pm.queryIntentActivities(pickWallpaperIntent, 0); - - // Get list of image picker intents - Intent pickImageIntent = new Intent(Intent.ACTION_GET_CONTENT); - pickImageIntent.setType("image/*"); - final List<ResolveInfo> imagePickerActivities = - pm.queryIntentActivities(pickImageIntent, 0); - final ComponentName[] imageActivities = new ComponentName[imagePickerActivities.size()]; - for (int i = 0; i < imagePickerActivities.size(); i++) { - ActivityInfo activityInfo = imagePickerActivities.get(i).activityInfo; - imageActivities[i] = new ComponentName(activityInfo.packageName, activityInfo.name); - } - - outerLoop: - for (ResolveInfo info : apps) { - final ComponentName itemComponentName = - new ComponentName(info.activityInfo.packageName, info.activityInfo.name); - final String itemPackageName = itemComponentName.getPackageName(); - // Exclude anything from our own package, and the old Launcher, - // and live wallpaper picker - if (itemPackageName.equals(context.getPackageName()) || - itemPackageName.equals("com.android.launcher") || - itemPackageName.equals("com.android.wallpaper.livepicker")) { - continue; - } - // Exclude any package that already responds to the image picker intent - for (ResolveInfo imagePickerActivityInfo : imagePickerActivities) { - if (itemPackageName.equals( - imagePickerActivityInfo.activityInfo.packageName)) { - continue outerLoop; - } - } - mThirdPartyWallpaperPickers.add(new ThirdPartyWallpaperTile(info)); - } - } - - public int getCount() { - return mThirdPartyWallpaperPickers.size(); - } - - public ThirdPartyWallpaperTile getItem(int position) { - return mThirdPartyWallpaperPickers.get(position); - } - - public long getItemId(int position) { - return position; - } - - public View getView(int position, View convertView, ViewGroup parent) { - View view; - - if (convertView == null) { - view = mInflater.inflate(R.layout.wallpaper_picker_third_party_item, parent, false); - } else { - view = convertView; - } - - ResolveInfo info = mThirdPartyWallpaperPickers.get(position).mResolveInfo; - TextView label = (TextView) view.findViewById(R.id.wallpaper_item_label); - label.setText(info.loadLabel(mPackageManager)); - Drawable icon = info.loadIcon(mPackageManager); - icon.setBounds(new Rect(0, 0, mIconSize, mIconSize)); - label.setCompoundDrawables(null, icon, null, null); - return view; - } -} diff --git a/WallpaperPicker/src/com/android/launcher3/ToggleOnTapCallback.java b/WallpaperPicker/src/com/android/launcher3/ToggleOnTapCallback.java new file mode 100644 index 000000000..2bc48ee17 --- /dev/null +++ b/WallpaperPicker/src/com/android/launcher3/ToggleOnTapCallback.java @@ -0,0 +1,67 @@ +package com.android.launcher3; + +import android.view.View; +import android.view.ViewPropertyAnimator; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; + +import com.android.launcher3.util.Thunk; + +/** + * Callback that toggles the visibility of the target view when crop view is tapped. + */ +public class ToggleOnTapCallback implements CropView.TouchCallback { + + @Thunk final View mViewtoToggle; + + private ViewPropertyAnimator mAnim; + private boolean mIgnoreNextTap; + + public ToggleOnTapCallback(View viewtoHide) { + mViewtoToggle = viewtoHide; + } + + @Override + public void onTouchDown() { + if (mAnim != null) { + mAnim.cancel(); + } + if (mViewtoToggle.getAlpha() == 1f) { + mIgnoreNextTap = true; + } + + mAnim = mViewtoToggle.animate(); + mAnim.alpha(0f) + .setDuration(150) + .withEndAction(new Runnable() { + public void run() { + mViewtoToggle.setVisibility(View.INVISIBLE); + } + }); + + mAnim.setInterpolator(new AccelerateInterpolator(0.75f)); + mAnim.start(); + } + + @Override + public void onTouchUp() { + mIgnoreNextTap = false; + } + + @Override + public void onTap() { + boolean ignoreTap = mIgnoreNextTap; + mIgnoreNextTap = false; + if (!ignoreTap) { + if (mAnim != null) { + mAnim.cancel(); + } + mViewtoToggle.setVisibility(View.VISIBLE); + mAnim = mViewtoToggle.animate(); + mAnim.alpha(1f) + .setDuration(150) + .setInterpolator(new DecelerateInterpolator(0.75f)); + mAnim.start(); + } + } +} diff --git a/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java b/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java index b717b597c..4be6f17ac 100644 --- a/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java +++ b/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java @@ -20,15 +20,14 @@ import android.annotation.TargetApi; import android.app.ActionBar; import android.app.Activity; import android.app.WallpaperManager; -import android.content.Context; import android.content.Intent; -import android.content.SharedPreferences; -import android.content.res.Configuration; +import android.content.pm.ActivityInfo; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Matrix; import android.graphics.Point; import android.graphics.RectF; +import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -58,24 +57,10 @@ import java.util.WeakHashMap; public class WallpaperCropActivity extends BaseActivity implements Handler.Callback { private static final String LOGTAG = "Launcher3.CropActivity"; - protected static final String WALLPAPER_WIDTH_KEY = WallpaperUtils.WALLPAPER_WIDTH_KEY; - protected static final String WALLPAPER_HEIGHT_KEY = WallpaperUtils.WALLPAPER_HEIGHT_KEY; - - /** - * The maximum bitmap size we allow to be returned through the intent. - * Intents have a maximum of 1MB in total size. However, the Bitmap seems to - * have some overhead to hit so that we go way below the limit here to make - * sure the intent stays below 1MB.We should consider just returning a byte - * array instead of a Bitmap instance to avoid overhead. - */ - public static final int MAX_BMAP_IN_INTENT = 750000; - public static final float WALLPAPER_SCREENS_SPAN = WallpaperUtils.WALLPAPER_SCREENS_SPAN; - private static final int MSG_LOAD_IMAGE = 1; protected CropView mCropView; protected View mProgressView; - protected Uri mUri; protected View mSetWallpaperButton; private HandlerThread mLoaderThread; @@ -96,7 +81,7 @@ public class WallpaperCropActivity extends BaseActivity implements Handler.Callb init(); if (!enableRotation()) { - setRequestedOrientation(Configuration.ORIENTATION_PORTRAIT); + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); } } @@ -123,12 +108,9 @@ public class WallpaperCropActivity extends BaseActivity implements Handler.Callb new View.OnClickListener() { @Override public void onClick(View v) { - boolean finishActivityWhenDone = true; // Never fade on finish because we return to the app that started us (e.g. // Photos), not the home screen. - boolean shouldFadeOutOnFinish = false; - cropImageAndSetWallpaper(imageUri, null, finishActivityWhenDone, - shouldFadeOutOnFinish); + cropImageAndSetWallpaper(imageUri, null, false /* shouldFadeOutOnFinish */); } }); mSetWallpaperButton = findViewById(R.id.set_wallpaper_button); @@ -169,52 +151,71 @@ public class WallpaperCropActivity extends BaseActivity implements Handler.Callb public boolean handleMessage(Message msg) { if (msg.what == MSG_LOAD_IMAGE) { final LoadRequest req = (LoadRequest) msg.obj; - try { - req.src.loadInBackground(new InBitmapProvider() { + final boolean loadSuccess; - @Override - public Bitmap forPixelCount(int count) { - Bitmap bitmapToReuse = null; - // Find the smallest bitmap that satisfies the pixel count limit - synchronized (mReusableBitmaps) { - int currentBitmapSize = Integer.MAX_VALUE; - for (Bitmap b : mReusableBitmaps) { - int bitmapSize = b.getWidth() * b.getHeight(); - if ((bitmapSize >= count) && (bitmapSize < currentBitmapSize)) { - bitmapToReuse = b; - currentBitmapSize = bitmapSize; + if (req.src == null) { + Drawable defaultWallpaper = WallpaperManager.getInstance(this) + .getBuiltInDrawable(mCropView.getWidth(), mCropView.getHeight(), + false, 0.5f, 0.5f); + + if (defaultWallpaper == null) { + loadSuccess = false; + Log.w(LOGTAG, "Null default wallpaper encountered."); + } else { + loadSuccess = true; + req.result = new DrawableTileSource(this, + defaultWallpaper, DrawableTileSource.MAX_PREVIEW_SIZE); + } + } else { + try { + req.src.loadInBackground(new InBitmapProvider() { + + @Override + public Bitmap forPixelCount(int count) { + Bitmap bitmapToReuse = null; + // Find the smallest bitmap that satisfies the pixel count limit + synchronized (mReusableBitmaps) { + int currentBitmapSize = Integer.MAX_VALUE; + for (Bitmap b : mReusableBitmaps) { + int bitmapSize = b.getWidth() * b.getHeight(); + if ((bitmapSize >= count) && (bitmapSize < currentBitmapSize)) { + bitmapToReuse = b; + currentBitmapSize = bitmapSize; + } } - } - if (bitmapToReuse != null) { - mReusableBitmaps.remove(bitmapToReuse); + if (bitmapToReuse != null) { + mReusableBitmaps.remove(bitmapToReuse); + } } + return bitmapToReuse; } - return bitmapToReuse; + }); + } catch (SecurityException securityException) { + if (isActivityDestroyed()) { + // Temporarily granted permissions are revoked when the activity + // finishes, potentially resulting in a SecurityException here. + // Even though {@link #isDestroyed} might also return true in different + // situations where the configuration changes, we are fine with + // catching these cases here as well. + return true; + } else { + // otherwise it had a different cause and we throw it further + throw securityException; } - }); - } catch (SecurityException securityException) { - if (isActivityDestroyed()) { - // Temporarily granted permissions are revoked when the activity - // finishes, potentially resulting in a SecurityException here. - // Even though {@link #isDestroyed} might also return true in different - // situations where the configuration changes, we are fine with - // catching these cases here as well. - return true; - } else { - // otherwise it had a different cause and we throw it further - throw securityException; } + + req.result = new BitmapRegionTileSource(getContext(), req.src, + mTempStorageForDecoding); + loadSuccess = req.src.getLoadingState() == BitmapSource.State.LOADED; } - req.result = new BitmapRegionTileSource(getContext(), req.src, mTempStorageForDecoding); runOnUiThread(new Runnable() { @Override public void run() { if (req == mCurrentLoadRequest) { - onLoadRequestComplete(req, - req.src.getLoadingState() == BitmapSource.State.LOADED); + onLoadRequestComplete(req, loadSuccess); } else { addReusableBitmap(req.result); } @@ -226,7 +227,7 @@ public class WallpaperCropActivity extends BaseActivity implements Handler.Callb } @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) - protected boolean isActivityDestroyed() { + public boolean isActivityDestroyed() { return Utilities.ATLEAST_JB_MR1 && isDestroyed(); } @@ -274,14 +275,16 @@ public class WallpaperCropActivity extends BaseActivity implements Handler.Callb mProgressView.setVisibility(View.GONE); } + @TargetApi(Build.VERSION_CODES.KITKAT) public final void setCropViewTileSource(BitmapSource bitmapSource, boolean touchEnabled, - boolean moveToLeft, CropViewScaleAndOffsetProvider scaleProvider, Runnable postExecute) { + boolean moveToLeft, CropViewScaleAndOffsetProvider scaleAndOffsetProvider, + Runnable postExecute) { final LoadRequest req = new LoadRequest(); req.moveToLeft = moveToLeft; req.src = bitmapSource; req.touchEnabled = touchEnabled; req.postExecute = postExecute; - req.scaleAndOffsetProvider = scaleProvider; + req.scaleAndOffsetProvider = scaleAndOffsetProvider; mCurrentLoadRequest = req; // Remove any pending requests @@ -305,62 +308,39 @@ public class WallpaperCropActivity extends BaseActivity implements Handler.Callb return getResources().getBoolean(R.bool.allow_rotation); } - protected void setWallpaper(Uri uri, final boolean finishActivityWhenDone, - final boolean shouldFadeOutOnFinish) { + public void setWallpaper(Uri uri, boolean shouldFadeOutOnFinish) { int rotation = BitmapUtils.getRotationFromExif(getContext(), uri); BitmapCropTask cropTask = new BitmapCropTask( getContext(), uri, null, rotation, 0, 0, true, false, null); - final Point bounds = cropTask.getImageBounds(); - BitmapCropTask.OnEndCropHandler onEndCrop = new BitmapCropTask.OnEndCropHandler() { - public void run(boolean cropSucceeded) { - updateWallpaperDimensions(bounds.x, bounds.y); - if (finishActivityWhenDone) { - setResult(Activity.RESULT_OK); - finish(); - if (cropSucceeded && shouldFadeOutOnFinish) { - overridePendingTransition(0, R.anim.fade_out); - } - } - } - }; - cropTask.setOnEndRunnable(onEndCrop); + BitmapCropTask.OnEndCropHandler onEndCrop = new CropAndFinishHandler( + cropTask.getImageBounds(), shouldFadeOutOnFinish); + cropTask.setOnEndCropHandler(onEndCrop); cropTask.setNoCrop(true); cropTask.execute(); } - protected void cropImageAndSetWallpaper(Resources res, int resId, - final boolean finishActivityWhenDone, final boolean shouldFadeOutOnFinish) { + public void cropImageAndSetWallpaper(Resources res, int resId, boolean shouldFadeOutOnFinish) { // crop this image and scale it down to the default wallpaper size for // this device - int rotation = BitmapUtils.getRotationFromExif(res, resId); + int rotation = BitmapUtils.getRotationFromExif(res, resId, this); Point inSize = mCropView.getSourceDimensions(); Point outSize = WallpaperUtils.getDefaultWallpaperSize(getResources(), getWindowManager()); RectF crop = Utils.getMaxCropRect( inSize.x, inSize.y, outSize.x, outSize.y, false); - BitmapCropTask.OnEndCropHandler onEndCrop = new BitmapCropTask.OnEndCropHandler() { - public void run(boolean cropSucceeded) { - // Passing 0, 0 will cause launcher to revert to using the - // default wallpaper size - updateWallpaperDimensions(0, 0); - if (finishActivityWhenDone) { - setResult(Activity.RESULT_OK); - finish(); - if (cropSucceeded && shouldFadeOutOnFinish) { - overridePendingTransition(0, R.anim.fade_out); - } - } - } - }; + // Passing 0, 0 will cause launcher to revert to using the + // default wallpaper size + CropAndFinishHandler onEndCrop = new CropAndFinishHandler(new Point(0, 0), + shouldFadeOutOnFinish); BitmapCropTask cropTask = new BitmapCropTask(getContext(), res, resId, crop, rotation, outSize.x, outSize.y, true, false, onEndCrop); cropTask.execute(); } @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) - protected void cropImageAndSetWallpaper(Uri uri, + public void cropImageAndSetWallpaper(Uri uri, BitmapCropTask.OnBitmapCroppedHandler onBitmapCroppedHandler, - final boolean finishActivityWhenDone, final boolean shouldFadeOutOnFinish) { + boolean shouldFadeOutOnFinish) { // Give some feedback so user knows something is happening. mProgressView.setVisibility(View.VISIBLE); @@ -436,21 +416,12 @@ public class WallpaperCropActivity extends BaseActivity implements Handler.Callb cropRect.top -= expandHeight; cropRect.bottom += expandHeight; } + final int outWidth = (int) Math.round(cropRect.width() * cropScale); final int outHeight = (int) Math.round(cropRect.height() * cropScale); + CropAndFinishHandler onEndCrop = new CropAndFinishHandler(new Point(outWidth, outHeight), + shouldFadeOutOnFinish); - BitmapCropTask.OnEndCropHandler onEndCrop = new BitmapCropTask.OnEndCropHandler() { - public void run(boolean cropSucceeded) { - updateWallpaperDimensions(outWidth, outHeight); - if (finishActivityWhenDone) { - setResult(Activity.RESULT_OK); - finish(); - } - if (cropSucceeded && shouldFadeOutOnFinish) { - overridePendingTransition(0, R.anim.fade_out); - } - } - }; BitmapCropTask cropTask = new BitmapCropTask(getContext(), uri, cropRect, cropRotation, outWidth, outHeight, true, false, onEndCrop); if (onBitmapCroppedHandler != null) { @@ -459,20 +430,30 @@ public class WallpaperCropActivity extends BaseActivity implements Handler.Callb cropTask.execute(); } - protected void updateWallpaperDimensions(int width, int height) { - String spKey = LauncherFiles.WALLPAPER_CROP_PREFERENCES_KEY; - SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_MULTI_PROCESS); - SharedPreferences.Editor editor = sp.edit(); - if (width != 0 && height != 0) { - editor.putInt(WALLPAPER_WIDTH_KEY, width); - editor.putInt(WALLPAPER_HEIGHT_KEY, height); - } else { - editor.remove(WALLPAPER_WIDTH_KEY); - editor.remove(WALLPAPER_HEIGHT_KEY); + public class CropAndFinishHandler implements BitmapCropTask.OnEndCropHandler { + private final Point mBounds; + private boolean mShouldFadeOutOnFinish; + + /** + * @param shouldFadeOutOnFinish Whether the wallpaper picker should override the default + * exit animation to fade out instead. This should only be set to true if the wallpaper + * preview will exactly match the actual wallpaper on the page we are returning to. + */ + public CropAndFinishHandler(Point bounds, boolean shouldFadeOutOnFinish) { + mBounds = bounds; + mShouldFadeOutOnFinish = shouldFadeOutOnFinish; + } + + @Override + public void run(boolean cropSucceeded) { + WallpaperUtils.saveWallpaperDimensions(mBounds.x, mBounds.y, + WallpaperCropActivity.this); + setResult(Activity.RESULT_OK); + finish(); + if (cropSucceeded && mShouldFadeOutOnFinish) { + overridePendingTransition(0, R.anim.fade_out); + } } - editor.commit(); - WallpaperUtils.suggestWallpaperDimension(getResources(), - sp, getWindowManager(), WallpaperManager.getInstance(getContext()), true); } static class LoadRequest { @@ -485,7 +466,7 @@ public class WallpaperCropActivity extends BaseActivity implements Handler.Callb TileSource result; } - interface CropViewScaleAndOffsetProvider { + public interface CropViewScaleAndOffsetProvider { float getScale(Point wallpaperSize, RectF crop); float getParallaxOffset(); } diff --git a/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java b/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java index b40e4bd80..27d60f8b8 100644 --- a/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java +++ b/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java @@ -16,38 +16,22 @@ package com.android.launcher3; -import android.Manifest; import android.animation.LayoutTransition; -import android.annotation.TargetApi; +import android.annotation.SuppressLint; import android.app.ActionBar; import android.app.Activity; -import android.app.WallpaperManager; -import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.Resources; -import android.database.Cursor; -import android.database.DataSetObserver; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.graphics.Canvas; -import android.graphics.Matrix; -import android.graphics.Point; -import android.graphics.PorterDuff; -import android.graphics.RectF; import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; import android.net.Uri; -import android.os.AsyncTask; -import android.os.Build; import android.os.Bundle; -import android.os.Process; -import android.provider.MediaStore; import android.util.Log; import android.util.Pair; import android.view.ActionMode; -import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; @@ -55,34 +39,30 @@ import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnLayoutChangeListener; +import android.view.View.OnLongClickListener; import android.view.ViewGroup; -import android.view.ViewPropertyAnimator; import android.view.ViewTreeObserver; import android.view.ViewTreeObserver.OnGlobalLayoutListener; import android.view.WindowManager; -import android.view.animation.AccelerateInterpolator; -import android.view.animation.DecelerateInterpolator; -import android.widget.ArrayAdapter; -import android.widget.BaseAdapter; -import android.widget.FrameLayout; import android.widget.HorizontalScrollView; -import android.widget.ImageView; import android.widget.LinearLayout; -import android.widget.Toast; -import com.android.gallery3d.common.BitmapCropTask; -import com.android.gallery3d.common.BitmapUtils; -import com.android.gallery3d.common.Utils; import com.android.launcher3.util.Thunk; -import com.android.photos.BitmapRegionTileSource; -import com.android.photos.BitmapRegionTileSource.BitmapSource; +import com.android.launcher3.wallpapertileinfo.DefaultWallpaperInfo; +import com.android.launcher3.wallpapertileinfo.FileWallpaperInfo; +import com.android.launcher3.wallpapertileinfo.LiveWallpaperInfo; +import com.android.launcher3.wallpapertileinfo.PickImageInfo; +import com.android.launcher3.wallpapertileinfo.ResourceWallpaperInfo; +import com.android.launcher3.wallpapertileinfo.ThirdPartyWallpaperInfo; +import com.android.launcher3.wallpapertileinfo.UriWallpaperInfo; +import com.android.launcher3.wallpapertileinfo.WallpaperTileInfo; import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; import java.util.ArrayList; +import java.util.List; -public class WallpaperPickerActivity extends WallpaperCropActivity { +public class WallpaperPickerActivity extends WallpaperCropActivity + implements OnClickListener, OnLongClickListener, ActionMode.Callback { static final String TAG = "Launcher.WallpaperPickerActivity"; public static final int IMAGE_PICK = 5; @@ -93,252 +73,22 @@ public class WallpaperPickerActivity extends WallpaperCropActivity { private static final String SELECTED_INDEX = "SELECTED_INDEX"; private static final int FLAG_POST_DELAY_MILLIS = 200; - @Thunk View mSelectedTile; - @Thunk boolean mIgnoreNextTap; - @Thunk OnClickListener mThumbnailOnClickListener; + @Thunk + View mSelectedTile; @Thunk LinearLayout mWallpapersView; @Thunk HorizontalScrollView mWallpaperScrollContainer; @Thunk View mWallpaperStrip; - @Thunk ActionMode.Callback mActionModeCallback; @Thunk ActionMode mActionMode; - @Thunk View.OnLongClickListener mLongClickListener; - ArrayList<Uri> mTempWallpaperTiles = new ArrayList<Uri>(); private SavedWallpaperImages mSavedImages; @Thunk int mSelectedIndex = -1; private float mWallpaperParallaxOffset; - public static abstract class WallpaperTileInfo { - protected View mView; - public Drawable mThumb; - - public void setView(View v) { - mView = v; - } - public void onClick(WallpaperPickerActivity a) {} - public void onSave(WallpaperPickerActivity a) {} - public void onDelete(WallpaperPickerActivity a) {} - public boolean isSelectable() { return false; } - public boolean isNamelessWallpaper() { return false; } - public void onIndexUpdated(CharSequence label) { - if (isNamelessWallpaper()) { - mView.setContentDescription(label); - } - } - } - - public static class PickImageInfo extends WallpaperTileInfo { - @Override - public void onClick(WallpaperPickerActivity a) { - Intent intent = new Intent(Intent.ACTION_GET_CONTENT); - intent.setType("image/*"); - a.startActivityForResultSafely(intent, IMAGE_PICK); - } - } - - public static class UriWallpaperInfo extends WallpaperTileInfo { - private Uri mUri; - public UriWallpaperInfo(Uri uri) { - mUri = uri; - } - @Override - public void onClick(final WallpaperPickerActivity a) { - a.setWallpaperButtonEnabled(false); - final BitmapRegionTileSource.UriBitmapSource bitmapSource = - new BitmapRegionTileSource.UriBitmapSource(a.getContext(), mUri); - a.setCropViewTileSource(bitmapSource, true, false, null, new Runnable() { - - @Override - public void run() { - if (bitmapSource.getLoadingState() == BitmapSource.State.LOADED) { - a.selectTile(mView); - a.setWallpaperButtonEnabled(true); - } else { - ViewGroup parent = (ViewGroup) mView.getParent(); - if (parent != null) { - parent.removeView(mView); - Toast.makeText(a.getContext(), R.string.image_load_fail, - Toast.LENGTH_SHORT).show(); - } - } - } - }); - } - @Override - public void onSave(final WallpaperPickerActivity a) { - boolean finishActivityWhenDone = true; - BitmapCropTask.OnBitmapCroppedHandler h = new BitmapCropTask.OnBitmapCroppedHandler() { - public void onBitmapCropped(byte[] imageBytes) { - Point thumbSize = getDefaultThumbnailSize(a.getResources()); - // rotation is set to 0 since imageBytes has already been correctly rotated - Bitmap thumb = createThumbnail( - thumbSize, null, null, imageBytes, null, 0, 0, true); - a.getSavedImages().writeImage(thumb, imageBytes); - } - }; - boolean shouldFadeOutOnFinish = a.getWallpaperParallaxOffset() == 0f; - a.cropImageAndSetWallpaper(mUri, h, finishActivityWhenDone, shouldFadeOutOnFinish); - } - @Override - public boolean isSelectable() { - return true; - } - @Override - public boolean isNamelessWallpaper() { - return true; - } - } - - public static class FileWallpaperInfo extends WallpaperTileInfo { - private File mFile; - - public FileWallpaperInfo(File target, Drawable thumb) { - mFile = target; - mThumb = thumb; - } - @Override - public void onClick(final WallpaperPickerActivity a) { - a.setWallpaperButtonEnabled(false); - final BitmapRegionTileSource.UriBitmapSource bitmapSource = - new BitmapRegionTileSource.UriBitmapSource(a.getContext(), Uri.fromFile(mFile)); - a.setCropViewTileSource(bitmapSource, false, true, null, new Runnable() { - - @Override - public void run() { - if (bitmapSource.getLoadingState() == BitmapSource.State.LOADED) { - a.setWallpaperButtonEnabled(true); - } - } - }); - } - @Override - public void onSave(WallpaperPickerActivity a) { - boolean shouldFadeOutOnFinish = a.getWallpaperParallaxOffset() == 0f; - a.setWallpaper(Uri.fromFile(mFile), true, shouldFadeOutOnFinish); - } - @Override - public boolean isSelectable() { - return true; - } - @Override - public boolean isNamelessWallpaper() { - return true; - } - } - - public static class ResourceWallpaperInfo extends WallpaperTileInfo { - private Resources mResources; - private int mResId; - - public ResourceWallpaperInfo(Resources res, int resId, Drawable thumb) { - mResources = res; - mResId = resId; - mThumb = thumb; - } - @Override - public void onClick(final WallpaperPickerActivity a) { - a.setWallpaperButtonEnabled(false); - final BitmapRegionTileSource.ResourceBitmapSource bitmapSource = - new BitmapRegionTileSource.ResourceBitmapSource(mResources, mResId); - a.setCropViewTileSource(bitmapSource, false, false, new CropViewScaleAndOffsetProvider() { - - @Override - public float getScale(Point wallpaperSize, RectF crop) { - return wallpaperSize.x /crop.width(); - } - - @Override - public float getParallaxOffset() { - return a.getWallpaperParallaxOffset(); - } - }, new Runnable() { - - @Override - public void run() { - if (bitmapSource.getLoadingState() == BitmapSource.State.LOADED) { - a.setWallpaperButtonEnabled(true); - } - } - }); - } - @Override - public void onSave(WallpaperPickerActivity a) { - boolean finishActivityWhenDone = true; - boolean shouldFadeOutOnFinish = true; - a.cropImageAndSetWallpaper(mResources, mResId, finishActivityWhenDone, - shouldFadeOutOnFinish); - } - @Override - public boolean isSelectable() { - return true; - } - @Override - public boolean isNamelessWallpaper() { - return true; - } - } - - @TargetApi(Build.VERSION_CODES.KITKAT) - public static class DefaultWallpaperInfo extends WallpaperTileInfo { - public DefaultWallpaperInfo(Drawable thumb) { - mThumb = thumb; - } - @Override - public void onClick(WallpaperPickerActivity a) { - CropView c = a.getCropView(); - Drawable defaultWallpaper = WallpaperManager.getInstance(a.getContext()) - .getBuiltInDrawable(c.getWidth(), c.getHeight(), false, 0.5f, 0.5f); - if (defaultWallpaper == null) { - Log.w(TAG, "Null default wallpaper encountered."); - c.setTileSource(null, null); - return; - } - - LoadRequest req = new LoadRequest(); - req.moveToLeft = false; - req.touchEnabled = false; - req.scaleAndOffsetProvider = new CropViewScaleAndOffsetProvider() { - - @Override - public float getScale(Point wallpaperSize, RectF crop) { - return 1f; - } - - @Override - public float getParallaxOffset() { - return 0; - } - }; - req.result = new DrawableTileSource(a.getContext(), - defaultWallpaper, DrawableTileSource.MAX_PREVIEW_SIZE); - a.onLoadRequestComplete(req, true); - } - @Override - public void onSave(WallpaperPickerActivity a) { - try { - WallpaperManager.getInstance(a.getContext()).clear(); - a.setResult(Activity.RESULT_OK); - } catch (IOException e) { - Log.w("Setting wallpaper to default threw exception", e); - } - a.finish(); - } - @Override - public boolean isSelectable() { - return true; - } - @Override - public boolean isNamelessWallpaper() { - return true; - } - } - /** - * shows the system wallpaper behind the window and hides the {@link - * #mCropView} if visible + * shows the system wallpaper behind the window and hides the {@link #mCropView} if visible * @param visible should the system wallpaper be shown */ protected void setSystemWallpaperVisiblity(final boolean visible) { @@ -380,7 +130,9 @@ public class WallpaperPickerActivity extends WallpaperCropActivity { } } - // called by onCreate; this is subclassed to overwrite WallpaperCropActivity + /** + * called by onCreate; this is sub-classed to overwrite WallpaperCropActivity + */ protected void init() { setContentView(R.layout.wallpaper_picker); @@ -390,139 +142,39 @@ public class WallpaperPickerActivity extends WallpaperCropActivity { mProgressView = findViewById(R.id.loading); mWallpaperScrollContainer = (HorizontalScrollView) findViewById(R.id.wallpaper_scroll_container); mWallpaperStrip = findViewById(R.id.wallpaper_strip); - mCropView.setTouchCallback(new CropView.TouchCallback() { - ViewPropertyAnimator mAnim; - @Override - public void onTouchDown() { - if (mAnim != null) { - mAnim.cancel(); - } - if (mWallpaperStrip.getAlpha() == 1f) { - mIgnoreNextTap = true; - } - mAnim = mWallpaperStrip.animate(); - mAnim.alpha(0f) - .setDuration(150) - .withEndAction(new Runnable() { - public void run() { - mWallpaperStrip.setVisibility(View.INVISIBLE); - } - }); - mAnim.setInterpolator(new AccelerateInterpolator(0.75f)); - mAnim.start(); - } - @Override - public void onTouchUp() { - mIgnoreNextTap = false; - } - @Override - public void onTap() { - boolean ignoreTap = mIgnoreNextTap; - mIgnoreNextTap = false; - if (!ignoreTap) { - if (mAnim != null) { - mAnim.cancel(); - } - mWallpaperStrip.setVisibility(View.VISIBLE); - mAnim = mWallpaperStrip.animate(); - mAnim.alpha(1f) - .setDuration(150) - .setInterpolator(new DecelerateInterpolator(0.75f)); - mAnim.start(); - } - } - }); - - mThumbnailOnClickListener = new OnClickListener() { - public void onClick(View v) { - if (mActionMode != null) { - // When CAB is up, clicking toggles the item instead - if (v.isLongClickable()) { - mLongClickListener.onLongClick(v); - } - return; - } - setWallpaperButtonEnabled(true); - WallpaperTileInfo info = (WallpaperTileInfo) v.getTag(); - if (info.isSelectable() && v.getVisibility() == View.VISIBLE) { - selectTile(v); - } - info.onClick(WallpaperPickerActivity.this); - } - }; - mLongClickListener = new View.OnLongClickListener() { - // Called when the user long-clicks on someView - public boolean onLongClick(View view) { - CheckableFrameLayout c = (CheckableFrameLayout) view; - c.toggle(); - - if (mActionMode != null) { - mActionMode.invalidate(); - } else { - // Start the CAB using the ActionMode.Callback defined below - mActionMode = startActionMode(mActionModeCallback); - int childCount = mWallpapersView.getChildCount(); - for (int i = 0; i < childCount; i++) { - mWallpapersView.getChildAt(i).setSelected(false); - } - } - return true; - } - }; + mCropView.setTouchCallback(new ToggleOnTapCallback(mWallpaperStrip)); mWallpaperParallaxOffset = getIntent().getFloatExtra(EXTRA_WALLPAPER_OFFSET, 0); - // Populate the built-in wallpapers - ArrayList<WallpaperTileInfo> wallpapers = findBundledWallpapers(); mWallpapersView = (LinearLayout) findViewById(R.id.wallpaper_list); - SimpleWallpapersAdapter ia = new SimpleWallpapersAdapter(getContext(), wallpapers); - populateWallpapersFromAdapter(mWallpapersView, ia, false); - // Populate the saved wallpapers mSavedImages = new SavedWallpaperImages(getContext()); - mSavedImages.loadThumbnailsAndImageIdList(); - populateWallpapersFromAdapter(mWallpapersView, mSavedImages, true); - - // Populate the live wallpapers - final LinearLayout liveWallpapersView = - (LinearLayout) findViewById(R.id.live_wallpaper_list); - final LiveWallpaperListAdapter a = new LiveWallpaperListAdapter(getContext()); - a.registerDataSetObserver(new DataSetObserver() { - public void onChanged() { - liveWallpapersView.removeAllViews(); - populateWallpapersFromAdapter(liveWallpapersView, a, false); + populateWallpapers(mWallpapersView, mSavedImages.loadThumbnailsAndImageIdList(), true); + + // Populate the built-in wallpapers + ArrayList<WallpaperTileInfo> wallpapers = findBundledWallpapers(); + populateWallpapers(mWallpapersView, wallpapers, false); + + // Load live wallpapers asynchronously + new LiveWallpaperInfo.LoaderTask(this) { + + @Override + protected void onPostExecute(List<LiveWallpaperInfo> result) { + populateWallpapers((LinearLayout) findViewById(R.id.live_wallpaper_list), + result, false); initializeScrollForRtl(); updateTileIndices(); } - }); + }.execute(); // Populate the third-party wallpaper pickers - final LinearLayout thirdPartyWallpapersView = - (LinearLayout) findViewById(R.id.third_party_wallpaper_list); - final ThirdPartyWallpaperPickerListAdapter ta = - new ThirdPartyWallpaperPickerListAdapter(getContext()); - populateWallpapersFromAdapter(thirdPartyWallpapersView, ta, false); + populateWallpapers((LinearLayout) findViewById(R.id.third_party_wallpaper_list), + ThirdPartyWallpaperInfo.getAll(this), false /* addLongPressHandler */); // Add a tile for the Gallery LinearLayout masterWallpaperList = (LinearLayout) findViewById(R.id.master_wallpaper_list); - FrameLayout pickImageTile = (FrameLayout) getLayoutInflater(). - inflate(R.layout.wallpaper_picker_image_picker_item, masterWallpaperList, false); - masterWallpaperList.addView(pickImageTile, 0); - - // Make its background the last photo taken on external storage - Bitmap lastPhoto = getThumbnailOfLastPhoto(); - if (lastPhoto != null) { - ImageView galleryThumbnailBg = - (ImageView) pickImageTile.findViewById(R.id.wallpaper_image); - galleryThumbnailBg.setImageBitmap(lastPhoto); - int colorOverlay = getResources().getColor(R.color.wallpaper_picker_translucent_gray); - galleryThumbnailBg.setColorFilter(colorOverlay, PorterDuff.Mode.SRC_ATOP); - } - - PickImageInfo pickImageInfo = new PickImageInfo(); - pickImageTile.setTag(pickImageInfo); - pickImageInfo.setView(pickImageTile); - pickImageTile.setOnClickListener(mThumbnailOnClickListener); + masterWallpaperList.addView( + createTileView(masterWallpaperList, new PickImageInfo(), false), 0); // Select the first item; wait for a layout pass so that we initialize the dimensions of // cropView or the defaultWallpaperView first @@ -532,8 +184,7 @@ public class WallpaperPickerActivity extends WallpaperCropActivity { int oldLeft, int oldTop, int oldRight, int oldBottom) { if ((right - left) > 0 && (bottom - top) > 0) { if (mSelectedIndex >= 0 && mSelectedIndex < mWallpapersView.getChildCount()) { - mThumbnailOnClickListener.onClick( - mWallpapersView.getChildAt(mSelectedIndex)); + onClick(mWallpapersView.getChildAt(mSelectedIndex)); setSystemWallpaperVisiblity(false); } v.removeOnLayoutChangeListener(this); @@ -577,95 +228,47 @@ public class WallpaperPickerActivity extends WallpaperCropActivity { } }); mSetWallpaperButton = findViewById(R.id.set_wallpaper_button); + } - // CAB for deleting items - mActionModeCallback = new ActionMode.Callback() { - // Called when the action mode is created; startActionMode() was called - @Override - public boolean onCreateActionMode(ActionMode mode, Menu menu) { - // Inflate a menu resource providing context menu items - MenuInflater inflater = mode.getMenuInflater(); - inflater.inflate(R.menu.cab_delete_wallpapers, menu); - return true; - } - - private int numCheckedItems() { - int childCount = mWallpapersView.getChildCount(); - int numCheckedItems = 0; - for (int i = 0; i < childCount; i++) { - CheckableFrameLayout c = (CheckableFrameLayout) mWallpapersView.getChildAt(i); - if (c.isChecked()) { - numCheckedItems++; - } - } - return numCheckedItems; - } - - // Called each time the action mode is shown. Always called after onCreateActionMode, - // but may be called multiple times if the mode is invalidated. - @Override - public boolean onPrepareActionMode(ActionMode mode, Menu menu) { - int numCheckedItems = numCheckedItems(); - if (numCheckedItems == 0) { - mode.finish(); - return true; - } else { - mode.setTitle(getResources().getQuantityString( - R.plurals.number_of_items_selected, numCheckedItems, numCheckedItems)); - return true; - } + /** + * Called when a wallpaper tile is clicked + */ + @Override + public void onClick(View v) { + if (mActionMode != null) { + // When CAB is up, clicking toggles the item instead + if (v.isLongClickable()) { + onLongClick(v); } + return; + } + setWallpaperButtonEnabled(true); + WallpaperTileInfo info = (WallpaperTileInfo) v.getTag(); + if (info.isSelectable() && v.getVisibility() == View.VISIBLE) { + selectTile(v); + } + info.onClick(this); + } - // Called when the user selects a contextual menu item - @Override - public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - int itemId = item.getItemId(); - if (itemId == R.id.menu_delete) { - int childCount = mWallpapersView.getChildCount(); - ArrayList<View> viewsToRemove = new ArrayList<View>(); - boolean selectedTileRemoved = false; - for (int i = 0; i < childCount; i++) { - CheckableFrameLayout c = - (CheckableFrameLayout) mWallpapersView.getChildAt(i); - if (c.isChecked()) { - WallpaperTileInfo info = (WallpaperTileInfo) c.getTag(); - info.onDelete(WallpaperPickerActivity.this); - viewsToRemove.add(c); - if (i == mSelectedIndex) { - selectedTileRemoved = true; - } - } - } - for (View v : viewsToRemove) { - mWallpapersView.removeView(v); - } - if (selectedTileRemoved) { - mSelectedIndex = -1; - mSelectedTile = null; - setSystemWallpaperVisiblity(true); - } - updateTileIndices(); - mode.finish(); // Action picked, so close the CAB - return true; - } else { - return false; - } - } + /** + * Called when a view is long clicked + */ + @Override + public boolean onLongClick(View v) { + CheckableFrameLayout c = (CheckableFrameLayout) v; + c.toggle(); - // Called when the user exits the action mode - @Override - public void onDestroyActionMode(ActionMode mode) { - int childCount = mWallpapersView.getChildCount(); - for (int i = 0; i < childCount; i++) { - CheckableFrameLayout c = (CheckableFrameLayout) mWallpapersView.getChildAt(i); - c.setChecked(false); - } - if (mSelectedTile != null) { - mSelectedTile.setSelected(true); - } - mActionMode = null; + if (mActionMode != null) { + mActionMode.invalidate(); + } else { + // Start the CAB using the ActionMode.Callback defined below + mActionMode = startActionMode(this); + int childCount = mWallpapersView.getChildCount(); + for (int i = 0; i < childCount; i++) { + mWallpapersView.getChildAt(i).setSelected(false); } - }; + } + return true; } public void setWallpaperButtonEnabled(boolean enabled) { @@ -676,7 +279,7 @@ public class WallpaperPickerActivity extends WallpaperCropActivity { return mWallpaperParallaxOffset; } - @Thunk void selectTile(View v) { + public void selectTile(View v) { if (mSelectedTile != null) { mSelectedTile.setSelected(false); mSelectedTile = null; @@ -704,35 +307,6 @@ public class WallpaperPickerActivity extends WallpaperCropActivity { } } - protected Bitmap getThumbnailOfLastPhoto() { - boolean canReadExternalStorage = getActivity().checkPermission( - Manifest.permission.READ_EXTERNAL_STORAGE, Process.myPid(), Process.myUid()) == - PackageManager.PERMISSION_GRANTED; - - if (!canReadExternalStorage) { - // MediaStore.Images.Media.EXTERNAL_CONTENT_URI requires - // the READ_EXTERNAL_STORAGE permission - return null; - } - - Cursor cursor = MediaStore.Images.Media.query(getContext().getContentResolver(), - MediaStore.Images.Media.EXTERNAL_CONTENT_URI, - new String[] { MediaStore.Images.ImageColumns._ID, - MediaStore.Images.ImageColumns.DATE_TAKEN}, - null, null, MediaStore.Images.ImageColumns.DATE_TAKEN + " DESC LIMIT 1"); - - Bitmap thumb = null; - if (cursor != null) { - if (cursor.moveToNext()) { - int id = cursor.getInt(0); - thumb = MediaStore.Images.Thumbnails.getThumbnail(getContext().getContentResolver(), - id, MediaStore.Images.Thumbnails.MINI_KIND, null); - } - cursor.close(); - } - return thumb; - } - public void onStop() { super.onStop(); mWallpaperStrip = findViewById(R.id.wallpaper_strip); @@ -755,21 +329,6 @@ public class WallpaperPickerActivity extends WallpaperCropActivity { mSelectedIndex = savedInstanceState.getInt(SELECTED_INDEX, -1); } - @Thunk void populateWallpapersFromAdapter(ViewGroup parent, BaseAdapter adapter, - boolean addLongPressHandler) { - for (int i = 0; i < adapter.getCount(); i++) { - FrameLayout thumbnail = (FrameLayout) adapter.getView(i, null, parent); - parent.addView(thumbnail, i); - WallpaperTileInfo info = (WallpaperTileInfo) adapter.getItem(i); - thumbnail.setTag(info); - info.setView(thumbnail); - if (addLongPressHandler) { - addLongPressHandler(thumbnail); - } - thumbnail.setOnClickListener(mThumbnailOnClickListener); - } - } - @Thunk void updateTileIndices() { LinearLayout masterWallpaperList = (LinearLayout) findViewById(R.id.master_wallpaper_list); final int childCount = masterWallpaperList.getChildCount(); @@ -811,126 +370,68 @@ public class WallpaperPickerActivity extends WallpaperCropActivity { } } - @Thunk static Point getDefaultThumbnailSize(Resources res) { - return new Point(res.getDimensionPixelSize(R.dimen.wallpaperThumbnailWidth), - res.getDimensionPixelSize(R.dimen.wallpaperThumbnailHeight)); - - } - - @Thunk static Bitmap createThumbnail(Point size, Context context, Uri uri, byte[] imageBytes, - Resources res, int resId, int rotation, boolean leftAligned) { - int width = size.x; - int height = size.y; - - BitmapCropTask cropTask; - if (uri != null) { - cropTask = new BitmapCropTask( - context, uri, null, rotation, width, height, false, true, null); - } else if (imageBytes != null) { - cropTask = new BitmapCropTask( - imageBytes, null, rotation, width, height, false, true, null); - } else { - cropTask = new BitmapCropTask( - context, res, resId, null, rotation, width, height, false, true, null); - } - Point bounds = cropTask.getImageBounds(); - if (bounds == null || bounds.x == 0 || bounds.y == 0) { - return null; - } - - Matrix rotateMatrix = new Matrix(); - rotateMatrix.setRotate(rotation); - float[] rotatedBounds = new float[] { bounds.x, bounds.y }; - rotateMatrix.mapPoints(rotatedBounds); - rotatedBounds[0] = Math.abs(rotatedBounds[0]); - rotatedBounds[1] = Math.abs(rotatedBounds[1]); - - RectF cropRect = Utils.getMaxCropRect( - (int) rotatedBounds[0], (int) rotatedBounds[1], width, height, leftAligned); - cropTask.setCropBounds(cropRect); - - if (cropTask.cropBitmap()) { - return cropTask.getCroppedBitmap(); - } else { - return null; - } - } - private void addTemporaryWallpaperTile(final Uri uri, boolean fromRestore) { + // Add a tile for the image picked from Gallery, reusing the existing tile if there is one. - FrameLayout existingImageThumbnail = null; + View imageTile = null; int indexOfExistingTile = 0; for (; indexOfExistingTile < mWallpapersView.getChildCount(); indexOfExistingTile++) { - FrameLayout thumbnail = (FrameLayout) mWallpapersView.getChildAt(indexOfExistingTile); + View thumbnail = mWallpapersView.getChildAt(indexOfExistingTile); Object tag = thumbnail.getTag(); if (tag instanceof UriWallpaperInfo && ((UriWallpaperInfo) tag).mUri.equals(uri)) { - existingImageThumbnail = thumbnail; + imageTile = thumbnail; break; } } - final FrameLayout pickedImageThumbnail; - if (existingImageThumbnail != null) { - pickedImageThumbnail = existingImageThumbnail; + final UriWallpaperInfo info; + if (imageTile != null) { // Always move the existing wallpaper to the front so user can see it without scrolling. mWallpapersView.removeViewAt(indexOfExistingTile); - mWallpapersView.addView(existingImageThumbnail, 0); + info = (UriWallpaperInfo) imageTile.getTag(); } else { // This is the first time this temporary wallpaper has been added - pickedImageThumbnail = (FrameLayout) getLayoutInflater() - .inflate(R.layout.wallpaper_picker_item, mWallpapersView, false); - pickedImageThumbnail.setVisibility(View.GONE); - mWallpapersView.addView(pickedImageThumbnail, 0); + info = new UriWallpaperInfo(uri); + imageTile = createTileView(mWallpapersView, info, true); mTempWallpaperTiles.add(uri); } + mWallpapersView.addView(imageTile, 0); + info.loadThumbnaleAsync(this); - // Load the thumbnail - final ImageView image = (ImageView) pickedImageThumbnail.findViewById(R.id.wallpaper_image); - final Point defaultSize = getDefaultThumbnailSize(this.getResources()); - final Context context = getContext(); - new AsyncTask<Void, Bitmap, Bitmap>() { - protected Bitmap doInBackground(Void...args) { - try { - int rotation = BitmapUtils.getRotationFromExif(context, uri); - return createThumbnail(defaultSize, context, uri, null, null, 0, rotation, - false); - } catch (SecurityException securityException) { - if (isActivityDestroyed()) { - // Temporarily granted permissions are revoked when the activity - // finishes, potentially resulting in a SecurityException here. - // Even though {@link #isDestroyed} might also return true in different - // situations where the configuration changes, we are fine with - // catching these cases here as well. - cancel(false); - } else { - // otherwise it had a different cause and we throw it further - throw securityException; - } - return null; - } - } - protected void onPostExecute(Bitmap thumb) { - if (!isCancelled() && thumb != null) { - image.setImageBitmap(thumb); - Drawable thumbDrawable = image.getDrawable(); - thumbDrawable.setDither(true); - pickedImageThumbnail.setVisibility(View.VISIBLE); - } else { - Log.e(TAG, "Error loading thumbnail for uri=" + uri); - } - } - }.execute(); - - UriWallpaperInfo info = new UriWallpaperInfo(uri); - pickedImageThumbnail.setTag(info); - info.setView(pickedImageThumbnail); - addLongPressHandler(pickedImageThumbnail); updateTileIndices(); - pickedImageThumbnail.setOnClickListener(mThumbnailOnClickListener); if (!fromRestore) { - mThumbnailOnClickListener.onClick(pickedImageThumbnail); + onClick(imageTile); } } + @Thunk void populateWallpapers(ViewGroup parent, List<? extends WallpaperTileInfo> wallpapers, + boolean addLongPressHandler) { + for (WallpaperTileInfo info : wallpapers) { + parent.addView(createTileView(parent, info, addLongPressHandler)); + } + } + + private View createTileView(ViewGroup parent, WallpaperTileInfo info, boolean addLongPress) { + View view = info.createView(this, getLayoutInflater(), parent); + view.setTag(info); + + if (addLongPress) { + view.setOnLongClickListener(this); + + // Enable stylus button to also trigger long click. + final StylusEventHelper stylusEventHelper = + new StylusEventHelper(new SimpleOnStylusPressListener(view), view); + view.setOnTouchListener(new View.OnTouchListener() { + @SuppressLint("ClickableViewAccessibility") + @Override + public boolean onTouch(View view, MotionEvent event) { + return stylusEventHelper.onMotionEvent(event); + } + }); + } + view.setOnClickListener(this); + return view; + } + public void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == IMAGE_PICK && resultCode == Activity.RESULT_OK) { if (data != null && data.getData() != null) { @@ -945,19 +446,6 @@ public class WallpaperPickerActivity extends WallpaperCropActivity { } } - private void addLongPressHandler(View v) { - v.setOnLongClickListener(mLongClickListener); - - // Enable stylus button to also trigger long click. - final StylusEventHelper stylusEventHelper = new StylusEventHelper(v); - v.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View view, MotionEvent event) { - return stylusEventHelper.checkAndPerformStylusEvent(event); - } - }); - } - private ArrayList<WallpaperTileInfo> findBundledWallpapers() { final PackageManager pm = getContext().getPackageManager(); final ArrayList<WallpaperTileInfo> bundled = new ArrayList<WallpaperTileInfo>(24); @@ -994,7 +482,8 @@ public class WallpaperPickerActivity extends WallpaperCropActivity { File thumbnail = new File(systemDir, name + "_small" + extension); Bitmap thumb = BitmapFactory.decodeFile(thumbnail.getAbsolutePath()); if (thumb != null) { - bundled.add(new FileWallpaperInfo(file, new BitmapDrawable(thumb))); + bundled.add(new FileWallpaperInfo( + file, new BitmapDrawable(getResources(), thumb))); } } } @@ -1012,8 +501,7 @@ public class WallpaperPickerActivity extends WallpaperCropActivity { if (partner == null || !partner.hideDefaultWallpaper()) { // Add an entry for the default wallpaper (stored in system resources) - WallpaperTileInfo defaultWallpaperInfo = Utilities.ATLEAST_KITKAT - ? getDefaultWallpaper() : getPreKKDefaultWallpaperInfo(); + WallpaperTileInfo defaultWallpaperInfo = DefaultWallpaperInfo.get(this); if (defaultWallpaperInfo != null) { bundled.add(0, defaultWallpaperInfo); } @@ -1021,95 +509,6 @@ public class WallpaperPickerActivity extends WallpaperCropActivity { return bundled; } - private boolean writeImageToFileAsJpeg(File f, Bitmap b) { - try { - f.createNewFile(); - FileOutputStream thumbFileStream = - getContext().openFileOutput(f.getName(), Context.MODE_PRIVATE); - b.compress(Bitmap.CompressFormat.JPEG, 95, thumbFileStream); - thumbFileStream.close(); - return true; - } catch (IOException e) { - Log.e(TAG, "Error while writing bitmap to file " + e); - f.delete(); - } - return false; - } - - private File getDefaultThumbFile() { - return new File(getContext().getFilesDir(), Build.VERSION.SDK_INT - + "_" + LauncherFiles.DEFAULT_WALLPAPER_THUMBNAIL); - } - - private boolean saveDefaultWallpaperThumb(Bitmap b) { - // Delete old thumbnails. - new File(getContext().getFilesDir(), LauncherFiles.DEFAULT_WALLPAPER_THUMBNAIL_OLD).delete(); - new File(getContext().getFilesDir(), LauncherFiles.DEFAULT_WALLPAPER_THUMBNAIL).delete(); - - for (int i = Build.VERSION_CODES.JELLY_BEAN; i < Build.VERSION.SDK_INT; i++) { - new File(getContext().getFilesDir(), i + "_" - + LauncherFiles.DEFAULT_WALLPAPER_THUMBNAIL).delete(); - } - return writeImageToFileAsJpeg(getDefaultThumbFile(), b); - } - - private ResourceWallpaperInfo getPreKKDefaultWallpaperInfo() { - Resources sysRes = Resources.getSystem(); - int resId = sysRes.getIdentifier("default_wallpaper", "drawable", "android"); - - File defaultThumbFile = getDefaultThumbFile(); - Bitmap thumb = null; - boolean defaultWallpaperExists = false; - if (defaultThumbFile.exists()) { - thumb = BitmapFactory.decodeFile(defaultThumbFile.getAbsolutePath()); - defaultWallpaperExists = true; - } else { - Resources res = getResources(); - Point defaultThumbSize = getDefaultThumbnailSize(res); - int rotation = BitmapUtils.getRotationFromExif(res, resId); - thumb = createThumbnail( - defaultThumbSize, getContext(), null, null, sysRes, resId, rotation, false); - if (thumb != null) { - defaultWallpaperExists = saveDefaultWallpaperThumb(thumb); - } - } - if (defaultWallpaperExists) { - return new ResourceWallpaperInfo(sysRes, resId, new BitmapDrawable(thumb)); - } - return null; - } - - @TargetApi(Build.VERSION_CODES.KITKAT) - private DefaultWallpaperInfo getDefaultWallpaper() { - File defaultThumbFile = getDefaultThumbFile(); - Bitmap thumb = null; - boolean defaultWallpaperExists = false; - if (defaultThumbFile.exists()) { - thumb = BitmapFactory.decodeFile(defaultThumbFile.getAbsolutePath()); - defaultWallpaperExists = true; - } else { - Resources res = getResources(); - Point defaultThumbSize = getDefaultThumbnailSize(res); - Drawable wallpaperDrawable = WallpaperManager.getInstance(getContext()).getBuiltInDrawable( - defaultThumbSize.x, defaultThumbSize.y, true, 0.5f, 0.5f); - if (wallpaperDrawable != null) { - thumb = Bitmap.createBitmap( - defaultThumbSize.x, defaultThumbSize.y, Bitmap.Config.ARGB_8888); - Canvas c = new Canvas(thumb); - wallpaperDrawable.setBounds(0, 0, defaultThumbSize.x, defaultThumbSize.y); - wallpaperDrawable.draw(c); - c.setBitmap(null); - } - if (thumb != null) { - defaultWallpaperExists = saveDefaultWallpaperThumb(thumb); - } - } - if (defaultWallpaperExists) { - return new DefaultWallpaperInfo(new BitmapDrawable(thumb)); - } - return null; - } - public Pair<ApplicationInfo, Integer> getWallpaperArrayResourceId() { // Context.getPackageName() may return the "original" package name, // com.android.launcher3; Resources needs the real package name, @@ -1144,62 +543,111 @@ public class WallpaperPickerActivity extends WallpaperCropActivity { } } - public CropView getCropView() { - return mCropView; - } - public SavedWallpaperImages getSavedImages() { return mSavedImages; } - private static class SimpleWallpapersAdapter extends ArrayAdapter<WallpaperTileInfo> { - private final LayoutInflater mLayoutInflater; - - SimpleWallpapersAdapter(Context context, ArrayList<WallpaperTileInfo> wallpapers) { - super(context, R.layout.wallpaper_picker_item, wallpapers); - mLayoutInflater = LayoutInflater.from(context); - } + public void startActivityForResultSafely(Intent intent, int requestCode) { + Utilities.startActivityForResultSafely(getActivity(), intent, requestCode); + } - public View getView(int position, View convertView, ViewGroup parent) { - Drawable thumb = getItem(position).mThumb; - if (thumb == null) { - Log.e(TAG, "Error decoding thumbnail for wallpaper #" + position); - } - return createImageTileView(mLayoutInflater, convertView, parent, thumb); - } + @Override + public boolean enableRotation() { + return super.enableRotation() || + getContentResolver().call(LauncherSettings.Settings.CONTENT_URI, + LauncherSettings.Settings.METHOD_GET_BOOLEAN, + Utilities.ALLOW_ROTATION_PREFERENCE_KEY, new Bundle()) + .getBoolean(LauncherSettings.Settings.EXTRA_VALUE); } - public static View createImageTileView(LayoutInflater layoutInflater, - View convertView, ViewGroup parent, Drawable thumb) { - View view; + // CAB for deleting items + /** + * Called when the action mode is created; startActionMode() was called + */ + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + // Inflate a menu resource providing context menu items + MenuInflater inflater = mode.getMenuInflater(); + inflater.inflate(R.menu.cab_delete_wallpapers, menu); + return true; + } - if (convertView == null) { - view = layoutInflater.inflate(R.layout.wallpaper_picker_item, parent, false); - } else { - view = convertView; + /** + * Called each time the action mode is shown. Always called after onCreateActionMode, + * but may be called multiple times if the mode is invalidated. + */ + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + int childCount = mWallpapersView.getChildCount(); + int numCheckedItems = 0; + for (int i = 0; i < childCount; i++) { + CheckableFrameLayout c = (CheckableFrameLayout) mWallpapersView.getChildAt(i); + if (c.isChecked()) { + numCheckedItems++; + } } - ImageView image = (ImageView) view.findViewById(R.id.wallpaper_image); - - if (thumb != null) { - image.setImageDrawable(thumb); - thumb.setDither(true); + if (numCheckedItems == 0) { + mode.finish(); + return true; + } else { + mode.setTitle(getResources().getQuantityString( + R.plurals.number_of_items_selected, numCheckedItems, numCheckedItems)); + return true; } - - return view; - } - - public void startActivityForResultSafely(Intent intent, int requestCode) { - Utilities.startActivityForResultSafely(getActivity(), intent, requestCode); } + /** + * Called when the user selects a contextual menu item + */ @Override - public boolean enableRotation() { - // Check if rotation is enabled for this device. - if (Utilities.isRotationAllowedForDevice(getContext())) + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + int itemId = item.getItemId(); + if (itemId == R.id.menu_delete) { + int childCount = mWallpapersView.getChildCount(); + ArrayList<View> viewsToRemove = new ArrayList<View>(); + boolean selectedTileRemoved = false; + for (int i = 0; i < childCount; i++) { + CheckableFrameLayout c = + (CheckableFrameLayout) mWallpapersView.getChildAt(i); + if (c.isChecked()) { + WallpaperTileInfo info = (WallpaperTileInfo) c.getTag(); + info.onDelete(WallpaperPickerActivity.this); + viewsToRemove.add(c); + if (i == mSelectedIndex) { + selectedTileRemoved = true; + } + } + } + for (View v : viewsToRemove) { + mWallpapersView.removeView(v); + } + if (selectedTileRemoved) { + mSelectedIndex = -1; + mSelectedTile = null; + setSystemWallpaperVisiblity(true); + } + updateTileIndices(); + mode.finish(); // Action picked, so close the CAB return true; + } else { + return false; + } + } - // Check if the user has specifically enabled rotation via preferences. - return Utilities.isAllowRotationPrefEnabled(getApplicationContext(), true); + /** + * Called when the user exits the action mode + */ + @Override + public void onDestroyActionMode(ActionMode mode) { + int childCount = mWallpapersView.getChildCount(); + for (int i = 0; i < childCount; i++) { + CheckableFrameLayout c = (CheckableFrameLayout) mWallpapersView.getChildAt(i); + c.setChecked(false); + } + if (mSelectedTile != null) { + mSelectedTile.setSelected(true); + } + mActionMode = null; } } diff --git a/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/DefaultWallpaperInfo.java b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/DefaultWallpaperInfo.java new file mode 100644 index 000000000..7ede26096 --- /dev/null +++ b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/DefaultWallpaperInfo.java @@ -0,0 +1,164 @@ +package com.android.launcher3.wallpapertileinfo; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.app.WallpaperManager; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Point; +import android.graphics.RectF; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.util.Log; + +import com.android.gallery3d.common.BitmapUtils; +import com.android.launcher3.LauncherFiles; +import com.android.launcher3.Utilities; +import com.android.launcher3.WallpaperCropActivity.CropViewScaleAndOffsetProvider; +import com.android.launcher3.WallpaperPickerActivity; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; + +public class DefaultWallpaperInfo extends DrawableThumbWallpaperInfo { + + private static final String TAG = "DefaultWallpaperInfo"; + + public DefaultWallpaperInfo(Drawable thumb) { + super(thumb); + } + + @Override + public void onClick(WallpaperPickerActivity a) { + a.setCropViewTileSource(null, false, false, new CropViewScaleAndOffsetProvider() { + + @Override + public float getScale(Point wallpaperSize, RectF crop) { + return 1f; + } + + @Override + public float getParallaxOffset() { + return 0; + } + }, null); + } + + @Override + public void onSave(WallpaperPickerActivity a) { + try { + WallpaperManager.getInstance(a.getContext()).clear(); + a.setResult(Activity.RESULT_OK); + } catch (IOException e) { + Log.w(TAG, "Setting wallpaper to default threw exception", e); + } + a.finish(); + } + + @Override + public boolean isSelectable() { + return true; + } + + @Override + public boolean isNamelessWallpaper() { + return true; + } + + /** + * @return the system default wallpaper tile or null + */ + public static WallpaperTileInfo get(Context context) { + return Utilities.ATLEAST_KITKAT + ? getDefaultWallpaper(context) : getPreKKDefaultWallpaperInfo(context); + } + + @TargetApi(Build.VERSION_CODES.KITKAT) + private static DefaultWallpaperInfo getDefaultWallpaper(Context context) { + File defaultThumbFile = getDefaultThumbFile(context); + Bitmap thumb = null; + boolean defaultWallpaperExists = false; + Resources res = context.getResources(); + + if (defaultThumbFile.exists()) { + thumb = BitmapFactory.decodeFile(defaultThumbFile.getAbsolutePath()); + defaultWallpaperExists = true; + } else { + Point defaultThumbSize = getDefaultThumbSize(res); + Drawable wallpaperDrawable = WallpaperManager.getInstance(context).getBuiltInDrawable( + defaultThumbSize.x, defaultThumbSize.y, true, 0.5f, 0.5f); + if (wallpaperDrawable != null) { + thumb = Bitmap.createBitmap( + defaultThumbSize.x, defaultThumbSize.y, Bitmap.Config.ARGB_8888); + Canvas c = new Canvas(thumb); + wallpaperDrawable.setBounds(0, 0, defaultThumbSize.x, defaultThumbSize.y); + wallpaperDrawable.draw(c); + c.setBitmap(null); + } + if (thumb != null) { + defaultWallpaperExists = saveDefaultWallpaperThumb(context, thumb); + } + } + if (defaultWallpaperExists) { + return new DefaultWallpaperInfo(new BitmapDrawable(res, thumb)); + } + return null; + } + + private static ResourceWallpaperInfo getPreKKDefaultWallpaperInfo(Context context) { + Resources sysRes = Resources.getSystem(); + Resources res = context.getResources(); + + int resId = sysRes.getIdentifier("default_wallpaper", "drawable", "android"); + + File defaultThumbFile = getDefaultThumbFile(context); + Bitmap thumb = null; + boolean defaultWallpaperExists = false; + if (defaultThumbFile.exists()) { + thumb = BitmapFactory.decodeFile(defaultThumbFile.getAbsolutePath()); + defaultWallpaperExists = true; + } else { + int rotation = BitmapUtils.getRotationFromExif(res, resId, context); + thumb = createThumbnail(context, null, null, sysRes, resId, rotation, false); + if (thumb != null) { + defaultWallpaperExists = saveDefaultWallpaperThumb(context, thumb); + } + } + if (defaultWallpaperExists) { + return new ResourceWallpaperInfo(sysRes, resId, new BitmapDrawable(res, thumb)); + } + return null; + } + + private static File getDefaultThumbFile(Context context) { + return new File(context.getFilesDir(), Build.VERSION.SDK_INT + + "_" + LauncherFiles.DEFAULT_WALLPAPER_THUMBNAIL); + } + + private static boolean saveDefaultWallpaperThumb(Context c, Bitmap b) { + // Delete old thumbnails. + new File(c.getFilesDir(), LauncherFiles.DEFAULT_WALLPAPER_THUMBNAIL_OLD).delete(); + new File(c.getFilesDir(), LauncherFiles.DEFAULT_WALLPAPER_THUMBNAIL).delete(); + + for (int i = Build.VERSION_CODES.JELLY_BEAN; i < Build.VERSION.SDK_INT; i++) { + new File(c.getFilesDir(), i + "_" + LauncherFiles.DEFAULT_WALLPAPER_THUMBNAIL).delete(); + } + File f = getDefaultThumbFile(c); + try { + f.createNewFile(); + FileOutputStream thumbFileStream = c.openFileOutput(f.getName(), Context.MODE_PRIVATE); + b.compress(Bitmap.CompressFormat.JPEG, 95, thumbFileStream); + thumbFileStream.close(); + return true; + } catch (IOException e) { + Log.e(TAG, "Error while writing bitmap to file " + e); + f.delete(); + return false; + } + } +}
\ No newline at end of file diff --git a/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/DrawableThumbWallpaperInfo.java b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/DrawableThumbWallpaperInfo.java new file mode 100644 index 000000000..a55375ddf --- /dev/null +++ b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/DrawableThumbWallpaperInfo.java @@ -0,0 +1,37 @@ +package com.android.launcher3.wallpapertileinfo; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; + +import com.android.launcher3.R; + +/** + * WallpaperTileInfo which uses drawable as the thumbnail. + */ +public abstract class DrawableThumbWallpaperInfo extends WallpaperTileInfo { + + private final Drawable mThumb; + + DrawableThumbWallpaperInfo(Drawable thumb) { + mThumb = thumb; + } + + @Override + public View createView(Context context, LayoutInflater inflator, ViewGroup parent) { + mView = inflator.inflate(R.layout.wallpaper_picker_item, parent, false); + setThumb(mThumb); + return mView; + } + + public void setThumb(Drawable thumb) { + if (mView != null && thumb != null) { + thumb.setDither(true); + ImageView image = (ImageView) mView.findViewById(R.id.wallpaper_image); + image.setImageDrawable(thumb); + } + } +} diff --git a/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/FileWallpaperInfo.java b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/FileWallpaperInfo.java new file mode 100644 index 000000000..f6a46fc48 --- /dev/null +++ b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/FileWallpaperInfo.java @@ -0,0 +1,52 @@ +package com.android.launcher3.wallpapertileinfo; + +import android.graphics.drawable.Drawable; +import android.net.Uri; + +import com.android.launcher3.WallpaperPickerActivity; +import com.android.photos.BitmapRegionTileSource; +import com.android.photos.BitmapRegionTileSource.BitmapSource; + +import java.io.File; + +public class FileWallpaperInfo extends DrawableThumbWallpaperInfo { + + private final File mFile; + + public FileWallpaperInfo(File target, Drawable thumb) { + super(thumb); + mFile = target; + } + + @Override + public void onClick(final WallpaperPickerActivity a) { + a.setWallpaperButtonEnabled(false); + final BitmapRegionTileSource.FilePathBitmapSource bitmapSource = + new BitmapRegionTileSource.FilePathBitmapSource(mFile.getAbsolutePath()); + a.setCropViewTileSource(bitmapSource, false, true, null, new Runnable() { + + @Override + public void run() { + if (bitmapSource.getLoadingState() == BitmapSource.State.LOADED) { + a.setWallpaperButtonEnabled(true); + } + } + }); + } + + @Override + public void onSave(WallpaperPickerActivity a) { + boolean shouldFadeOutOnFinish = a.getWallpaperParallaxOffset() == 0f; + a.setWallpaper(Uri.fromFile(mFile), shouldFadeOutOnFinish); + } + + @Override + public boolean isSelectable() { + return true; + } + + @Override + public boolean isNamelessWallpaper() { + return true; + } +}
\ No newline at end of file diff --git a/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/LiveWallpaperInfo.java b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/LiveWallpaperInfo.java new file mode 100644 index 000000000..d800ba6e1 --- /dev/null +++ b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/LiveWallpaperInfo.java @@ -0,0 +1,118 @@ +package com.android.launcher3.wallpapertileinfo; + +import android.app.WallpaperInfo; +import android.app.WallpaperManager; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.graphics.drawable.Drawable; +import android.os.AsyncTask; +import android.service.wallpaper.WallpaperService; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import com.android.launcher3.R; +import com.android.launcher3.WallpaperPickerActivity; + +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.text.Collator; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +public class LiveWallpaperInfo extends WallpaperTileInfo { + + private static final String TAG = "LiveWallpaperTile"; + + private Drawable mThumbnail; + private WallpaperInfo mInfo; + + public LiveWallpaperInfo(Drawable thumbnail, WallpaperInfo info, Intent intent) { + mThumbnail = thumbnail; + mInfo = info; + } + + @Override + public void onClick(WallpaperPickerActivity a) { + Intent preview = new Intent(WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER); + preview.putExtra(WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT, + mInfo.getComponent()); + a.startActivityForResultSafely(preview, + WallpaperPickerActivity.PICK_WALLPAPER_THIRD_PARTY_ACTIVITY); + } + + @Override + public View createView(Context context, LayoutInflater inflator, ViewGroup parent) { + mView = inflator.inflate(R.layout.wallpaper_picker_live_wallpaper_item, parent, false); + + ImageView image = (ImageView) mView.findViewById(R.id.wallpaper_image); + ImageView icon = (ImageView) mView.findViewById(R.id.wallpaper_icon); + if (mThumbnail != null) { + image.setImageDrawable(mThumbnail); + icon.setVisibility(View.GONE); + } else { + icon.setImageDrawable(mInfo.loadIcon(context.getPackageManager())); + icon.setVisibility(View.VISIBLE); + } + + TextView label = (TextView) mView.findViewById(R.id.wallpaper_item_label); + label.setText(mInfo.loadLabel(context.getPackageManager())); + return mView; + } + + /** + * An async task to load various live wallpaper tiles. + */ + public static class LoaderTask extends AsyncTask<Void, Void, List<LiveWallpaperInfo>> { + private final Context mContext; + + public LoaderTask(Context context) { + mContext = context; + } + + @Override + protected List<LiveWallpaperInfo> doInBackground(Void... params) { + final PackageManager pm = mContext.getPackageManager(); + + List<ResolveInfo> list = pm.queryIntentServices( + new Intent(WallpaperService.SERVICE_INTERFACE), + PackageManager.GET_META_DATA); + + Collections.sort(list, new Comparator<ResolveInfo>() { + final Collator mCollator = Collator.getInstance(); + + public int compare(ResolveInfo info1, ResolveInfo info2) { + return mCollator.compare(info1.loadLabel(pm), info2.loadLabel(pm)); + } + }); + + List<LiveWallpaperInfo> result = new ArrayList<>(); + + for (ResolveInfo resolveInfo : list) { + WallpaperInfo info = null; + try { + info = new WallpaperInfo(mContext, resolveInfo); + } catch (XmlPullParserException | IOException e) { + Log.w(TAG, "Skipping wallpaper " + resolveInfo.serviceInfo, e); + continue; + } + + + Drawable thumb = info.loadThumbnail(pm); + Intent launchIntent = new Intent(WallpaperService.SERVICE_INTERFACE); + launchIntent.setClassName(info.getPackageName(), info.getServiceName()); + result.add(new LiveWallpaperInfo(thumb, info, launchIntent)); + } + + return result; + } + } +}
\ No newline at end of file diff --git a/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/PickImageInfo.java b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/PickImageInfo.java new file mode 100644 index 000000000..9d8cc1cd7 --- /dev/null +++ b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/PickImageInfo.java @@ -0,0 +1,74 @@ +package com.android.launcher3.wallpapertileinfo; + +import android.Manifest; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.PorterDuff; +import android.os.Process; +import android.provider.MediaStore; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; + +import com.android.launcher3.R; +import com.android.launcher3.WallpaperPickerActivity; + +public class PickImageInfo extends WallpaperTileInfo { + + @Override + public void onClick(WallpaperPickerActivity a) { + Intent intent = new Intent(Intent.ACTION_GET_CONTENT).setType("image/*"); + a.startActivityForResultSafely(intent, WallpaperPickerActivity.IMAGE_PICK); + } + + @Override + public View createView(Context context, LayoutInflater inflator, ViewGroup parent) { + mView = inflator.inflate(R.layout.wallpaper_picker_image_picker_item, parent, false); + + // Make its background the last photo taken on external storage + Bitmap lastPhoto = getThumbnailOfLastPhoto(context); + if (lastPhoto != null) { + ImageView galleryThumbnailBg = + (ImageView) mView.findViewById(R.id.wallpaper_image); + galleryThumbnailBg.setImageBitmap(lastPhoto); + int colorOverlay = context.getResources().getColor(R.color.wallpaper_picker_translucent_gray); + galleryThumbnailBg.setColorFilter(colorOverlay, PorterDuff.Mode.SRC_ATOP); + } + + mView.setTag(this); + return mView; + } + + private Bitmap getThumbnailOfLastPhoto(Context context) { + boolean canReadExternalStorage = context.checkPermission( + Manifest.permission.READ_EXTERNAL_STORAGE, Process.myPid(), Process.myUid()) == + PackageManager.PERMISSION_GRANTED; + + if (!canReadExternalStorage) { + // MediaStore.Images.Media.EXTERNAL_CONTENT_URI requires + // the READ_EXTERNAL_STORAGE permission + return null; + } + + Cursor cursor = MediaStore.Images.Media.query(context.getContentResolver(), + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, + new String[] { MediaStore.Images.ImageColumns._ID, + MediaStore.Images.ImageColumns.DATE_TAKEN}, + null, null, MediaStore.Images.ImageColumns.DATE_TAKEN + " DESC LIMIT 1"); + + Bitmap thumb = null; + if (cursor != null) { + if (cursor.moveToNext()) { + int id = cursor.getInt(0); + thumb = MediaStore.Images.Thumbnails.getThumbnail(context.getContentResolver(), + id, MediaStore.Images.Thumbnails.MINI_KIND, null); + } + cursor.close(); + } + return thumb; + } +}
\ No newline at end of file diff --git a/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/ResourceWallpaperInfo.java b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/ResourceWallpaperInfo.java new file mode 100644 index 000000000..d63714c22 --- /dev/null +++ b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/ResourceWallpaperInfo.java @@ -0,0 +1,65 @@ +package com.android.launcher3.wallpapertileinfo; + +import android.content.res.Resources; +import android.graphics.Point; +import android.graphics.RectF; +import android.graphics.drawable.Drawable; + +import com.android.launcher3.WallpaperCropActivity.CropViewScaleAndOffsetProvider; +import com.android.launcher3.WallpaperPickerActivity; +import com.android.photos.BitmapRegionTileSource; +import com.android.photos.BitmapRegionTileSource.BitmapSource; + +public class ResourceWallpaperInfo extends DrawableThumbWallpaperInfo { + + private final Resources mResources; + private final int mResId; + + public ResourceWallpaperInfo(Resources res, int resId, Drawable thumb) { + super(thumb); + mResources = res; + mResId = resId; + } + + @Override + public void onClick(final WallpaperPickerActivity a) { + a.setWallpaperButtonEnabled(false); + final BitmapRegionTileSource.ResourceBitmapSource bitmapSource = + new BitmapRegionTileSource.ResourceBitmapSource(mResources, mResId, a); + a.setCropViewTileSource(bitmapSource, false, false, new CropViewScaleAndOffsetProvider() { + + @Override + public float getScale(Point wallpaperSize, RectF crop) { + return wallpaperSize.x /crop.width(); + } + + @Override + public float getParallaxOffset() { + return a.getWallpaperParallaxOffset(); + } + }, new Runnable() { + + @Override + public void run() { + if (bitmapSource.getLoadingState() == BitmapSource.State.LOADED) { + a.setWallpaperButtonEnabled(true); + } + } + }); + } + + @Override + public void onSave(WallpaperPickerActivity a) { + a.cropImageAndSetWallpaper(mResources, mResId, true /* shouldFadeOutOnFinish */); + } + + @Override + public boolean isSelectable() { + return true; + } + + @Override + public boolean isNamelessWallpaper() { + return true; + } +}
\ No newline at end of file diff --git a/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/ThirdPartyWallpaperInfo.java b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/ThirdPartyWallpaperInfo.java new file mode 100644 index 000000000..5e2538f84 --- /dev/null +++ b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/ThirdPartyWallpaperInfo.java @@ -0,0 +1,78 @@ +package com.android.launcher3.wallpapertileinfo; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import com.android.launcher3.R; +import com.android.launcher3.WallpaperPickerActivity; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; + +public class ThirdPartyWallpaperInfo extends WallpaperTileInfo { + + private final ResolveInfo mResolveInfo; + private final int mIconSize; + + public ThirdPartyWallpaperInfo(ResolveInfo resolveInfo, int iconSize) { + mResolveInfo = resolveInfo; + mIconSize = iconSize; + } + + @Override + public void onClick(WallpaperPickerActivity a) { + final ComponentName itemComponentName = new ComponentName( + mResolveInfo.activityInfo.packageName, mResolveInfo.activityInfo.name); + Intent launchIntent = new Intent(Intent.ACTION_SET_WALLPAPER) + .setComponent(itemComponentName) + .putExtra(WallpaperPickerActivity.EXTRA_WALLPAPER_OFFSET, + a.getWallpaperParallaxOffset()); + a.startActivityForResultSafely( + launchIntent, WallpaperPickerActivity.PICK_WALLPAPER_THIRD_PARTY_ACTIVITY); + } + + @Override + public View createView(Context context, LayoutInflater inflator, ViewGroup parent) { + mView = inflator.inflate(R.layout.wallpaper_picker_third_party_item, parent, false); + + TextView label = (TextView) mView.findViewById(R.id.wallpaper_item_label); + label.setText(mResolveInfo.loadLabel(context.getPackageManager())); + Drawable icon = mResolveInfo.loadIcon(context.getPackageManager()); + icon.setBounds(new Rect(0, 0, mIconSize, mIconSize)); + label.setCompoundDrawables(null, icon, null, null); + return mView; + } + + public static List<ThirdPartyWallpaperInfo> getAll(Context context) { + ArrayList<ThirdPartyWallpaperInfo> result = new ArrayList<>(); + int iconSize = context.getResources().getDimensionPixelSize(R.dimen.wallpaperItemIconSize); + + final PackageManager pm = context.getPackageManager(); + Intent pickImageIntent = new Intent(Intent.ACTION_GET_CONTENT).setType("image/*"); + HashSet<String> excludePackages = new HashSet<>(); + // Exclude packages which contain an image picker + for (ResolveInfo info : pm.queryIntentActivities(pickImageIntent, 0)) { + excludePackages.add(info.activityInfo.packageName); + } + excludePackages.add(context.getPackageName()); + excludePackages.add("com.android.wallpaper.livepicker"); + + final Intent pickWallpaperIntent = new Intent(Intent.ACTION_SET_WALLPAPER); + for (ResolveInfo info : pm.queryIntentActivities(pickWallpaperIntent, 0)) { + if (!excludePackages.contains(info.activityInfo.packageName)) { + result.add(new ThirdPartyWallpaperInfo(info, iconSize)); + } + } + return result; + } +}
\ No newline at end of file diff --git a/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/UriWallpaperInfo.java b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/UriWallpaperInfo.java new file mode 100644 index 000000000..180eb93e0 --- /dev/null +++ b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/UriWallpaperInfo.java @@ -0,0 +1,109 @@ +package com.android.launcher3.wallpapertileinfo; + +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.net.Uri; +import android.os.AsyncTask; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Toast; + +import com.android.gallery3d.common.BitmapCropTask; +import com.android.gallery3d.common.BitmapUtils; +import com.android.launcher3.R; +import com.android.launcher3.WallpaperPickerActivity; +import com.android.photos.BitmapRegionTileSource; +import com.android.photos.BitmapRegionTileSource.BitmapSource; + +public class UriWallpaperInfo extends DrawableThumbWallpaperInfo { + + private static final String TAG = "UriWallpaperInfo"; + + public final Uri mUri; + + public UriWallpaperInfo(Uri uri) { + super(null); + mUri = uri; + } + + @Override + public void onClick(final WallpaperPickerActivity a) { + a.setWallpaperButtonEnabled(false); + final BitmapRegionTileSource.UriBitmapSource bitmapSource = + new BitmapRegionTileSource.UriBitmapSource(a.getContext(), mUri); + a.setCropViewTileSource(bitmapSource, true, false, null, new Runnable() { + + @Override + public void run() { + if (bitmapSource.getLoadingState() == BitmapSource.State.LOADED) { + a.selectTile(mView); + a.setWallpaperButtonEnabled(true); + } else { + ViewGroup parent = (ViewGroup) mView.getParent(); + if (parent != null) { + parent.removeView(mView); + Toast.makeText(a.getContext(), R.string.image_load_fail, + Toast.LENGTH_SHORT).show(); + } + } + } + }); + } + + @Override + public void onSave(final WallpaperPickerActivity a) { + BitmapCropTask.OnBitmapCroppedHandler h = new BitmapCropTask.OnBitmapCroppedHandler() { + public void onBitmapCropped(byte[] imageBytes) { + // rotation is set to 0 since imageBytes has already been correctly rotated + Bitmap thumb = createThumbnail(a, null, imageBytes, null, 0, 0, true); + a.getSavedImages().writeImage(thumb, imageBytes); + } + }; + boolean shouldFadeOutOnFinish = a.getWallpaperParallaxOffset() == 0f; + a.cropImageAndSetWallpaper(mUri, h, shouldFadeOutOnFinish); + } + + @Override + public boolean isSelectable() { + return true; + } + + @Override + public boolean isNamelessWallpaper() { + return true; + } + + public void loadThumbnaleAsync(final WallpaperPickerActivity activity) { + mView.setVisibility(View.GONE); + new AsyncTask<Void, Void, Bitmap>() { + protected Bitmap doInBackground(Void...args) { + try { + int rotation = BitmapUtils.getRotationFromExif(activity, mUri); + return createThumbnail(activity, mUri, null, null, 0, rotation, false); + } catch (SecurityException securityException) { + if (activity.isActivityDestroyed()) { + // Temporarily granted permissions are revoked when the activity + // finishes, potentially resulting in a SecurityException here. + // Even though {@link #isDestroyed} might also return true in different + // situations where the configuration changes, we are fine with + // catching these cases here as well. + cancel(false); + } else { + // otherwise it had a different cause and we throw it further + throw securityException; + } + return null; + } + } + protected void onPostExecute(Bitmap thumb) { + if (!isCancelled() && thumb != null) { + setThumb(new BitmapDrawable(activity.getResources(), thumb)); + mView.setVisibility(View.VISIBLE); + } else { + Log.e(TAG, "Error loading thumbnail for uri=" + mUri); + } + } + }.execute(); + } +}
\ No newline at end of file diff --git a/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/WallpaperTileInfo.java b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/WallpaperTileInfo.java new file mode 100644 index 000000000..5fc317c3a --- /dev/null +++ b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/WallpaperTileInfo.java @@ -0,0 +1,87 @@ +package com.android.launcher3.wallpapertileinfo; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Matrix; +import android.graphics.Point; +import android.graphics.RectF; +import android.net.Uri; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.android.gallery3d.common.BitmapCropTask; +import com.android.gallery3d.common.Utils; +import com.android.launcher3.R; +import com.android.launcher3.WallpaperPickerActivity; + +public abstract class WallpaperTileInfo { + + protected View mView; + + public void onClick(WallpaperPickerActivity a) {} + + public void onSave(WallpaperPickerActivity a) {} + + public void onDelete(WallpaperPickerActivity a) {} + + public boolean isSelectable() { return false; } + + public boolean isNamelessWallpaper() { return false; } + + public void onIndexUpdated(CharSequence label) { + if (isNamelessWallpaper()) { + mView.setContentDescription(label); + } + } + + public abstract View createView(Context context, LayoutInflater inflator, ViewGroup parent); + + protected static Point getDefaultThumbSize(Resources res) { + return new Point(res.getDimensionPixelSize(R.dimen.wallpaperThumbnailWidth), + res.getDimensionPixelSize(R.dimen.wallpaperThumbnailHeight)); + + } + + protected static Bitmap createThumbnail(Context context, Uri uri, byte[] imageBytes, + Resources res, int resId, int rotation, boolean leftAligned) { + Point size = getDefaultThumbSize(context.getResources()); + int width = size.x; + int height = size.y; + + BitmapCropTask cropTask; + if (uri != null) { + cropTask = new BitmapCropTask( + context, uri, null, rotation, width, height, false, true, null); + } else if (imageBytes != null) { + cropTask = new BitmapCropTask( + imageBytes, null, rotation, width, height, false, true, null); + } else { + cropTask = new BitmapCropTask( + context, res, resId, null, rotation, width, height, false, true, null); + } + Point bounds = cropTask.getImageBounds(); + if (bounds == null || bounds.x == 0 || bounds.y == 0) { + return null; + } + + Matrix rotateMatrix = new Matrix(); + rotateMatrix.setRotate(rotation); + float[] rotatedBounds = new float[] { bounds.x, bounds.y }; + rotateMatrix.mapPoints(rotatedBounds); + rotatedBounds[0] = Math.abs(rotatedBounds[0]); + rotatedBounds[1] = Math.abs(rotatedBounds[1]); + + RectF cropRect = Utils.getMaxCropRect( + (int) rotatedBounds[0], (int) rotatedBounds[1], width, height, leftAligned); + cropTask.setCropBounds(cropRect); + + if (cropTask.cropBitmap()) { + return cropTask.getCroppedBitmap(); + } else { + return null; + } + } + +}
\ No newline at end of file diff --git a/WallpaperPicker/src/com/android/photos/BitmapRegionTileSource.java b/WallpaperPicker/src/com/android/photos/BitmapRegionTileSource.java index 2d496a5a6..2f9c9a343 100644 --- a/WallpaperPicker/src/com/android/photos/BitmapRegionTileSource.java +++ b/WallpaperPicker/src/com/android/photos/BitmapRegionTileSource.java @@ -31,8 +31,8 @@ import android.os.Build; import android.util.Log; import com.android.gallery3d.common.BitmapUtils; +import com.android.gallery3d.common.ExifOrientation; import com.android.gallery3d.common.Utils; -import com.android.gallery3d.exif.ExifInterface; import com.android.gallery3d.glrenderer.BasicTexture; import com.android.gallery3d.glrenderer.BitmapTexture; import com.android.photos.views.TiledImageRenderer; @@ -160,13 +160,7 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource { private State mState = State.NOT_LOADED; public boolean loadInBackground(InBitmapProvider bitmapProvider) { - ExifInterface ei = new ExifInterface(); - if (readExif(ei)) { - Integer ori = ei.getTagIntValue(ExifInterface.TAG_ORIENTATION); - if (ori != null) { - mRotation = ExifInterface.getRotationForOrientationValue(ori.shortValue()); - } - } + mRotation = getExifRotation(); mDecoder = loadBitmapRegionDecoder(); if (mDecoder == null) { mState = State.ERROR_LOADING; @@ -232,7 +226,7 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource { return mRotation; } - public abstract boolean readExif(ExifInterface ei); + public abstract int getExifRotation(); public abstract SimpleBitmapRegionDecoder loadBitmapRegionDecoder(); public abstract Bitmap loadPreviewBitmap(BitmapFactory.Options options); @@ -259,18 +253,10 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource { public Bitmap loadPreviewBitmap(BitmapFactory.Options options) { return BitmapFactory.decodeFile(mPath, options); } + @Override - public boolean readExif(ExifInterface ei) { - try { - ei.readExif(mPath); - return true; - } catch (NullPointerException e) { - Log.w("BitmapRegionTileSource", "reading exif failed", e); - return false; - } catch (IOException e) { - Log.w("BitmapRegionTileSource", "getting decoder failed", e); - return false; - } + public int getExifRotation() { + return ExifOrientation.readRotation(mPath); } } @@ -315,35 +301,22 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource { return null; } } + @Override - public boolean readExif(ExifInterface ei) { - InputStream is = null; - try { - is = regenerateInputStream(); - ei.readExif(is); - Utils.closeSilently(is); - return true; - } catch (FileNotFoundException e) { - Log.d("BitmapRegionTileSource", "Failed to load URI " + mUri, e); - return false; - } catch (IOException e) { - Log.d("BitmapRegionTileSource", "Failed to load URI " + mUri, e); - return false; - } catch (NullPointerException e) { - Log.d("BitmapRegionTileSource", "Failed to read EXIF for URI " + mUri, e); - return false; - } finally { - Utils.closeSilently(is); - } + public int getExifRotation() { + return BitmapUtils.getRotationFromExif(mContext, mUri); } } public static class ResourceBitmapSource extends BitmapSource { private Resources mRes; private int mResId; - public ResourceBitmapSource(Resources res, int resId) { + private Context mContext; + + public ResourceBitmapSource(Resources res, int resId, Context context) { mRes = res; mResId = resId; + mContext = context; } private InputStream regenerateInputStream() { InputStream is = mRes.openRawResource(mResId); @@ -366,17 +339,10 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource { public Bitmap loadPreviewBitmap(BitmapFactory.Options options) { return BitmapFactory.decodeResource(mRes, mResId, options); } + @Override - public boolean readExif(ExifInterface ei) { - try { - InputStream is = regenerateInputStream(); - ei.readExif(is); - Utils.closeSilently(is); - return true; - } catch (IOException e) { - Log.e("BitmapRegionTileSource", "Error reading resource", e); - return false; - } + public int getExifRotation() { + return BitmapUtils.getRotationFromExif(mRes, mResId, mContext); } } diff --git a/WallpaperPicker/src/com/android/photos/views/TiledImageView.java b/WallpaperPicker/src/com/android/photos/views/TiledImageView.java index 7e3e1a936..6f7a5303c 100644 --- a/WallpaperPicker/src/com/android/photos/views/TiledImageView.java +++ b/WallpaperPicker/src/com/android/photos/views/TiledImageView.java @@ -17,12 +17,7 @@ package com.android.photos.views; import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; import android.graphics.Matrix; -import android.graphics.Paint; -import android.graphics.Paint.Align; import android.graphics.RectF; import android.opengl.GLSurfaceView; import android.opengl.GLSurfaceView.Renderer; @@ -82,7 +77,6 @@ public class TiledImageView extends FrameLayout { mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); addView(mGLSurfaceView, new LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); - //setTileSource(new ColoredTiles()); } @Override @@ -247,66 +241,4 @@ public class TiledImageView extends FrameLayout { } } - - @SuppressWarnings("unused") - private static class ColoredTiles implements TileSource { - private static final int[] COLORS = new int[] { - Color.RED, - Color.BLUE, - Color.YELLOW, - Color.GREEN, - Color.CYAN, - Color.MAGENTA, - Color.WHITE, - }; - - private Paint mPaint = new Paint(); - private Canvas mCanvas = new Canvas(); - - @Override - public int getTileSize() { - return 256; - } - - @Override - public int getImageWidth() { - return 16384; - } - - @Override - public int getImageHeight() { - return 8192; - } - - @Override - public int getRotation() { - return 0; - } - - @Override - public Bitmap getTile(int level, int x, int y, Bitmap bitmap) { - int tileSize = getTileSize(); - if (bitmap == null) { - bitmap = Bitmap.createBitmap(tileSize, tileSize, - Bitmap.Config.ARGB_8888); - } - mCanvas.setBitmap(bitmap); - mCanvas.drawColor(COLORS[level]); - mPaint.setColor(Color.BLACK); - mPaint.setTextSize(20); - mPaint.setTextAlign(Align.CENTER); - mCanvas.drawText(x + "x" + y, 128, 128, mPaint); - tileSize <<= level; - x /= tileSize; - y /= tileSize; - mCanvas.drawText(x + "x" + y + " @ " + level, 128, 30, mPaint); - mCanvas.setBitmap(null); - return bitmap; - } - - @Override - public BasicTexture getPreview() { - return null; - } - } } |