diff options
160 files changed, 4025 insertions, 10173 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 99a6ad9aa..1f908d6ec 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -51,10 +51,8 @@ <uses-permission android:name="android.permission.CALL_PHONE" /> <uses-permission android:name="android.permission.SET_WALLPAPER" /> <uses-permission android:name="android.permission.SET_WALLPAPER_HINTS" /> - <uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.BIND_APPWIDGET" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> - <uses-permission android:name="android.permission.BROADCAST_STICKY"/> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="com.android.launcher.permission.READ_SETTINGS" /> <uses-permission android:name="com.android.launcher.permission.WRITE_SETTINGS" /> @@ -92,39 +90,6 @@ </intent-filter> </activity> - <!-- ENABLE_FOR_TESTING - - <activity - android:name="com.android.launcher3.testing.LauncherExtension" - android:launchMode="singleTask" - android:clearTaskOnLaunch="true" - android:stateNotNeeded="true" - android:theme="@style/Theme" - android:windowSoftInputMode="adjustPan" - android:screenOrientation="nosensor" - android:enabled="false"> - <intent-filter> - <action android:name="android.intent.action.MAIN" /> - <category android:name="android.intent.category.HOME" /> - <category android:name="android.intent.category.DEFAULT" /> - <category android:name="android.intent.category.MONKEY"/> - </intent-filter> - </activity> - - --> - - <activity - android:name="com.android.launcher3.ToggleWeightWatcher" - android:label="@string/toggle_weight_watcher" - android:enabled="@bool/debug_memory_enabled" - android:icon="@mipmap/ic_launcher_home"> - <intent-filter> - <action android:name="android.intent.action.MAIN" /> - <category android:name="android.intent.category.DEFAULT" /> - <category android:name="android.intent.category.LAUNCHER" /> - </intent-filter> - </activity> - <activity android:name="com.android.launcher3.WallpaperPickerActivity" android:theme="@style/Theme.WallpaperPicker" @@ -159,34 +124,6 @@ android:process=":settings_process"> </activity> - <!-- Debugging tools --> - <activity - android:name="com.android.launcher3.MemoryDumpActivity" - android:theme="@android:style/Theme.NoDisplay" - android:label="@string/debug_memory_activity" - android:enabled="@bool/debug_memory_enabled" - android:excludeFromRecents="true" - android:icon="@mipmap/ic_launcher_home" - > - <intent-filter> - <action android:name="android.intent.action.MAIN" /> - <category android:name="android.intent.category.DEFAULT" /> - <category android:name="android.intent.category.LAUNCHER" /> - </intent-filter> - </activity> - - <service android:name="com.android.launcher3.MemoryTracker" - android:enabled="@bool/debug_memory_enabled" - > - </service> - - <receiver - android:name="com.android.launcher3.WallpaperChangedReceiver"> - <intent-filter> - <action android:name="android.intent.action.WALLPAPER_CHANGED" /> - </intent-filter> - </receiver> - <!-- Intent received used to install shortcuts from other applications --> <receiver android:name="com.android.launcher3.InstallShortcutReceiver" @@ -203,12 +140,6 @@ </intent-filter> </receiver> - <receiver android:name="com.android.launcher3.StartupReceiver" > - <intent-filter> - <action android:name="android.intent.action.BOOT_COMPLETED" /> - </intent-filter> - </receiver> - <!-- The settings provider contains Home's data, like the workspace favorites --> <provider android:name="com.android.launcher3.LauncherProvider" @@ -219,5 +150,54 @@ <meta-data android:name="android.nfc.disable_beam_default" android:value="true" /> + + <!-- ENABLE_FOR_TESTING + + <activity + android:name="com.android.launcher3.testing.LauncherExtension" + android:launchMode="singleTask" + android:clearTaskOnLaunch="true" + android:stateNotNeeded="true" + android:theme="@style/Theme" + android:windowSoftInputMode="adjustPan" + android:screenOrientation="nosensor" + > + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.HOME" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.MONKEY"/> + </intent-filter> + </activity> + + <activity + android:name="com.android.launcher3.testing.MemoryDumpActivity" + android:theme="@android:style/Theme.NoDisplay" + android:label="* HPROF" + android:excludeFromRecents="true" + android:icon="@mipmap/ic_launcher_home" + > + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + + <activity + android:name="com.android.launcher3.testing.ToggleWeightWatcher" + android:label="Show Mem" + android:icon="@mipmap/ic_launcher_home"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + + <service android:name="com.android.launcher3.testing.MemoryTracker" /> + + --> + </application> </manifest> diff --git a/WallpaperPicker/res/anim/fade_out.xml b/WallpaperPicker/res/anim/fade_out.xml index 2749d9280..9ca7407e7 100644 --- a/WallpaperPicker/res/anim/fade_out.xml +++ b/WallpaperPicker/res/anim/fade_out.xml @@ -20,5 +20,4 @@ android:startOffset="@android:integer/config_longAnimTime" android:duration="@android:integer/config_mediumAnimTime" android:fromAlpha="1" - android:toAlpha="0"/> - + android:toAlpha="0"/>
\ No newline at end of file diff --git a/WallpaperPicker/src/com/android/gallery3d/common/BitmapCropTask.java b/WallpaperPicker/src/com/android/gallery3d/common/BitmapCropTask.java index a79fb65c9..47c40eb97 100644 --- a/WallpaperPicker/src/com/android/gallery3d/common/BitmapCropTask.java +++ b/WallpaperPicker/src/com/android/gallery3d/common/BitmapCropTask.java @@ -123,7 +123,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; - } - } } diff --git a/proguard.flags b/proguard.flags index 5a3dfd179..22ffa3c8c 100644 --- a/proguard.flags +++ b/proguard.flags @@ -44,7 +44,7 @@ public int getY(); } --keep class com.android.launcher3.DragLayer$LayoutParams { +-keep class com.android.launcher3.dragndrop.DragLayer$LayoutParams { public void setWidth(int); public int getWidth(); public void setHeight(int); diff --git a/res/animator-v21/overview_button_anim.xml b/res/animator-v21/overview_button_anim.xml new file mode 100644 index 000000000..aac3d2655 --- /dev/null +++ b/res/animator-v21/overview_button_anim.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright 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. +--> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + + <item android:state_pressed="true"> + <objectAnimator + android:duration="@android:integer/config_shortAnimTime" + android:propertyName="alpha" + android:valueTo="0.5" + android:valueType="floatType" /> + </item> + + <item android:state_focused="true"> + <objectAnimator + android:duration="@android:integer/config_shortAnimTime" + android:propertyName="alpha" + android:valueTo="0.5" + android:valueType="floatType" /> + </item> + <item> + <objectAnimator + android:duration="@android:integer/config_shortAnimTime" + android:propertyName="alpha" + android:valueTo="1" + android:valueType="floatType" /> + </item> + +</selector>
\ No newline at end of file diff --git a/res/drawable-hdpi/page_hover_left.9.png b/res/drawable-hdpi/page_hover_left.9.png Binary files differdeleted file mode 100644 index 3f11d0b33..000000000 --- a/res/drawable-hdpi/page_hover_left.9.png +++ /dev/null diff --git a/res/drawable-hdpi/page_hover_left_active.9.png b/res/drawable-hdpi/page_hover_left_active.9.png Binary files differdeleted file mode 100644 index abe4c31a9..000000000 --- a/res/drawable-hdpi/page_hover_left_active.9.png +++ /dev/null diff --git a/res/drawable-hdpi/page_hover_right.9.png b/res/drawable-hdpi/page_hover_right.9.png Binary files differdeleted file mode 100644 index 3bcf19195..000000000 --- a/res/drawable-hdpi/page_hover_right.9.png +++ /dev/null diff --git a/res/drawable-hdpi/page_hover_right_active.9.png b/res/drawable-hdpi/page_hover_right_active.9.png Binary files differdeleted file mode 100644 index 101e4dc0f..000000000 --- a/res/drawable-hdpi/page_hover_right_active.9.png +++ /dev/null diff --git a/res/drawable-mdpi/page_hover_left.9.png b/res/drawable-mdpi/page_hover_left.9.png Binary files differdeleted file mode 100644 index 2b6094cf6..000000000 --- a/res/drawable-mdpi/page_hover_left.9.png +++ /dev/null diff --git a/res/drawable-mdpi/page_hover_left_active.9.png b/res/drawable-mdpi/page_hover_left_active.9.png Binary files differdeleted file mode 100644 index 9eb00a282..000000000 --- a/res/drawable-mdpi/page_hover_left_active.9.png +++ /dev/null diff --git a/res/drawable-mdpi/page_hover_right.9.png b/res/drawable-mdpi/page_hover_right.9.png Binary files differdeleted file mode 100644 index c2e598351..000000000 --- a/res/drawable-mdpi/page_hover_right.9.png +++ /dev/null diff --git a/res/drawable-mdpi/page_hover_right_active.9.png b/res/drawable-mdpi/page_hover_right_active.9.png Binary files differdeleted file mode 100644 index d2771a1a5..000000000 --- a/res/drawable-mdpi/page_hover_right_active.9.png +++ /dev/null diff --git a/res/drawable-xhdpi/page_hover_left.9.png b/res/drawable-xhdpi/page_hover_left.9.png Binary files differdeleted file mode 100644 index dbcc0abd2..000000000 --- a/res/drawable-xhdpi/page_hover_left.9.png +++ /dev/null diff --git a/res/drawable-xhdpi/page_hover_left_active.9.png b/res/drawable-xhdpi/page_hover_left_active.9.png Binary files differdeleted file mode 100644 index 3233efe28..000000000 --- a/res/drawable-xhdpi/page_hover_left_active.9.png +++ /dev/null diff --git a/res/drawable-xhdpi/page_hover_right.9.png b/res/drawable-xhdpi/page_hover_right.9.png Binary files differdeleted file mode 100644 index d82f809bf..000000000 --- a/res/drawable-xhdpi/page_hover_right.9.png +++ /dev/null diff --git a/res/drawable-xhdpi/page_hover_right_active.9.png b/res/drawable-xhdpi/page_hover_right_active.9.png Binary files differdeleted file mode 100644 index 819ea195c..000000000 --- a/res/drawable-xhdpi/page_hover_right_active.9.png +++ /dev/null diff --git a/res/drawable-xxhdpi/page_hover_left.9.png b/res/drawable-xxhdpi/page_hover_left.9.png Binary files differdeleted file mode 100644 index c81f86c02..000000000 --- a/res/drawable-xxhdpi/page_hover_left.9.png +++ /dev/null diff --git a/res/drawable-xxhdpi/page_hover_left_active.9.png b/res/drawable-xxhdpi/page_hover_left_active.9.png Binary files differdeleted file mode 100644 index 858a3b2ab..000000000 --- a/res/drawable-xxhdpi/page_hover_left_active.9.png +++ /dev/null diff --git a/res/drawable-xxhdpi/page_hover_right.9.png b/res/drawable-xxhdpi/page_hover_right.9.png Binary files differdeleted file mode 100644 index c5297703e..000000000 --- a/res/drawable-xxhdpi/page_hover_right.9.png +++ /dev/null diff --git a/res/drawable-xxhdpi/page_hover_right_active.9.png b/res/drawable-xxhdpi/page_hover_right_active.9.png Binary files differdeleted file mode 100644 index 99005536b..000000000 --- a/res/drawable-xxhdpi/page_hover_right_active.9.png +++ /dev/null diff --git a/res/drawable/ic_setting_pressed.xml b/res/drawable/ic_setting_pressed.xml new file mode 100644 index 000000000..689f833ce --- /dev/null +++ b/res/drawable/ic_setting_pressed.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 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. +--> +<bitmap xmlns:android="http://schemas.android.com/apk/res/android" + android:alpha="0.5" + android:src="@drawable/ic_setting" /> diff --git a/res/drawable/ic_wallpaper_pressed.xml b/res/drawable/ic_wallpaper_pressed.xml new file mode 100644 index 000000000..d241c7d76 --- /dev/null +++ b/res/drawable/ic_wallpaper_pressed.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 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. +--> +<bitmap xmlns:android="http://schemas.android.com/apk/res/android" + android:alpha="0.5" + android:src="@drawable/ic_wallpaper" /> diff --git a/res/drawable/ic_widget_pressed.xml b/res/drawable/ic_widget_pressed.xml new file mode 100644 index 000000000..44ac5b6e4 --- /dev/null +++ b/res/drawable/ic_widget_pressed.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 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. +--> +<bitmap xmlns:android="http://schemas.android.com/apk/res/android" + android:alpha="0.5" + android:src="@drawable/ic_widget" /> diff --git a/res/layout-land/launcher.xml b/res/layout-land/launcher.xml index 6500ebcd2..951a30ec7 100644 --- a/res/layout-land/launcher.xml +++ b/res/layout-land/launcher.xml @@ -23,7 +23,7 @@ android:layout_height="match_parent" android:fitsSystemWindows="true"> - <com.android.launcher3.DragLayer + <com.android.launcher3.dragndrop.DragLayer android:id="@+id/drag_layer" android:layout_width="match_parent" android:layout_height="match_parent"> @@ -50,6 +50,10 @@ android:layout_gravity="right" /> <include + android:id="@+id/app_info_drop_target_bar" + layout="@layout/app_info_drop_target_bar" /> + + <include android:id="@+id/search_drop_target_bar" layout="@layout/search_drop_target_bar" /> @@ -68,7 +72,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="invisible" /> - </com.android.launcher3.DragLayer> + </com.android.launcher3.dragndrop.DragLayer> <ViewStub android:id="@+id/launcher_overlay_stub" diff --git a/res/layout-port/launcher.xml b/res/layout-port/launcher.xml index d0772ee70..4cb34e92e 100644 --- a/res/layout-port/launcher.xml +++ b/res/layout-port/launcher.xml @@ -24,7 +24,7 @@ android:layout_height="match_parent" android:fitsSystemWindows="true"> - <com.android.launcher3.DragLayer + <com.android.launcher3.dragndrop.DragLayer android:id="@+id/drag_layer" android:layout_width="match_parent" android:layout_height="match_parent"> @@ -64,6 +64,10 @@ android:layout_gravity="center_horizontal" /> <include + android:id="@+id/app_info_drop_target_bar" + layout="@layout/app_info_drop_target_bar" /> + + <include android:id="@+id/search_drop_target_bar" layout="@layout/search_drop_target_bar" /> @@ -78,7 +82,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="invisible" /> - </com.android.launcher3.DragLayer> + </com.android.launcher3.dragndrop.DragLayer> <ViewStub android:id="@+id/launcher_overlay_stub" diff --git a/res/layout-sw720dp/launcher.xml b/res/layout-sw720dp/launcher.xml index 802922ec1..3228999ce 100644 --- a/res/layout-sw720dp/launcher.xml +++ b/res/layout-sw720dp/launcher.xml @@ -23,7 +23,7 @@ android:layout_height="match_parent" android:fitsSystemWindows="true"> - <com.android.launcher3.DragLayer + <com.android.launcher3.dragndrop.DragLayer android:id="@+id/drag_layer" android:layout_width="match_parent" android:layout_height="match_parent"> @@ -50,6 +50,10 @@ android:layout_height="match_parent" /> <include + android:id="@+id/app_info_drop_target_bar" + layout="@layout/app_info_drop_target_bar" /> + + <include android:id="@+id/search_drop_target_bar" layout="@layout/search_drop_target_bar" /> @@ -76,7 +80,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="invisible" /> - </com.android.launcher3.DragLayer> + </com.android.launcher3.dragndrop.DragLayer> <ViewStub android:id="@+id/launcher_overlay_stub" diff --git a/res/layout-v21/overview_panel.xml b/res/layout-v21/overview_panel.xml new file mode 100644 index 000000000..fb6b512fc --- /dev/null +++ b/res/layout-v21/overview_panel.xml @@ -0,0 +1,69 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal|bottom" + android:gravity="top" + android:orientation="horizontal" > + + <TextView + android:id="@+id/wallpaper_button" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:drawablePadding="4dp" + android:drawableTop="@drawable/ic_wallpaper" + android:fontFamily="sans-serif-condensed" + android:gravity="center_horizontal" + android:stateListAnimator="@animator/overview_button_anim" + android:text="@string/wallpaper_button_text" + android:textAllCaps="true" + android:textColor="@android:color/white" + android:textSize="12sp" /> + + <TextView + android:id="@+id/widget_button" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:drawablePadding="4dp" + android:drawableTop="@drawable/ic_widget" + android:fontFamily="sans-serif-condensed" + android:gravity="center_horizontal" + android:stateListAnimator="@animator/overview_button_anim" + android:text="@string/widget_button_text" + android:textAllCaps="true" + android:textColor="@android:color/white" + android:textSize="12sp" /> + + <TextView + android:id="@+id/settings_button" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:drawablePadding="4dp" + android:drawableTop="@drawable/ic_setting" + android:fontFamily="sans-serif-condensed" + android:gravity="center_horizontal" + android:stateListAnimator="@animator/overview_button_anim" + android:text="@string/settings_button_text" + android:textAllCaps="true" + android:textColor="@android:color/white" + android:textSize="12sp" /> + +</LinearLayout>
\ No newline at end of file diff --git a/res/layout/app_info_drop_target_bar.xml b/res/layout/app_info_drop_target_bar.xml new file mode 100644 index 000000000..b8f30d0c8 --- /dev/null +++ b/res/layout/app_info_drop_target_bar.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2011 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<com.android.launcher3.AppInfoDropTargetBar xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:focusable="false" > + + <!-- Drag specific targets container --> + <LinearLayout + android:id="@+id/drag_target_bar" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_gravity="center|bottom" > + + <FrameLayout + style="@style/DropTargetButtonContainer" + android:layout_weight="1" > + + <!-- Info target --> + + <com.android.launcher3.InfoDropTarget + android:id="@+id/info_target_text" + style="@style/DropTargetButton" + android:text="@string/info_target_label" /> + </FrameLayout> + </LinearLayout> + +</com.android.launcher3.AppInfoDropTargetBar>
\ No newline at end of file diff --git a/res/layout/search_drop_target_bar.xml b/res/layout/search_drop_target_bar.xml index 4737ee1bc..724eb943d 100644 --- a/res/layout/search_drop_target_bar.xml +++ b/res/layout/search_drop_target_bar.xml @@ -43,18 +43,6 @@ style="@style/DropTargetButtonContainer" android:layout_weight="1" > - <!-- Info target --> - - <com.android.launcher3.InfoDropTarget - android:id="@+id/info_target_text" - style="@style/DropTargetButton" - android:text="@string/info_target_label" /> - </FrameLayout> - - <FrameLayout - style="@style/DropTargetButtonContainer" - android:layout_weight="1" > - <!-- Uninstall target --> <com.android.launcher3.UninstallDropTarget diff --git a/res/layout/dummy_widget.xml b/res/layout/zzz_dummy_widget.xml index a0fa8fc3e..a0fa8fc3e 100644 --- a/res/layout/dummy_widget.xml +++ b/res/layout/zzz_dummy_widget.xml diff --git a/res/layout/zzz_weight_watcher.xml b/res/layout/zzz_weight_watcher.xml new file mode 100644 index 000000000..07fd39e91 --- /dev/null +++ b/res/layout/zzz_weight_watcher.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<com.android.launcher3.testing.WeightWatcher xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" /> diff --git a/res/values-sw600dp/config.xml b/res/values-sw600dp/config.xml index a7345a705..eb9af9793 100644 --- a/res/values-sw600dp/config.xml +++ b/res/values-sw600dp/config.xml @@ -1,7 +1,4 @@ <resources> <bool name="is_tablet">true</bool> <bool name="allow_rotation">true</bool> - -<!-- DragController --> - <integer name="config_flingToDeleteMinVelocity">-1000</integer> </resources> diff --git a/res/values-sw600dp/dimens.xml b/res/values-sw600dp/dimens.xml index fb54f12a2..f4a4284a7 100644 --- a/res/values-sw600dp/dimens.xml +++ b/res/values-sw600dp/dimens.xml @@ -30,4 +30,10 @@ <dimen name="cling_migration_content_margin">64dp</dimen> <dimen name="cling_migration_content_width">280dp</dimen> +<!-- Widget tray --> + <dimen name="widget_section_indent">56dp</dimen> + + +<!-- DragController --> + <dimen name="drag_flingToDeleteMinVelocity">-1000dp</dimen> </resources> diff --git a/res/values/attrs.xml b/res/values/attrs.xml index 827332ad7..c2017e259 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -75,10 +75,6 @@ <!-- PagedView specific attributes. These attributes are used to customize a PagedView view in XML files. --> <declare-styleable name="PagedView"> - <!-- A spacing override for the icons within a page --> - <attr name="pageLayoutWidthGap" format="dimension" /> - <attr name="pageLayoutHeightGap" format="dimension" /> - <!-- The page indicator for this workspace --> <attr name="pageIndicator" format="reference" /> </declare-styleable> diff --git a/res/values/config.xml b/res/values/config.xml index 93c6d14f4..af4c96d5a 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -17,7 +17,6 @@ <bool name="enable_backup">false</bool> <!-- DragController --> - <integer name="config_flingToDeleteMinVelocity">-1500</integer> <item type="id" name="drag_event_parity" /> <!-- AllApps & Launcher transitions --> @@ -67,20 +66,16 @@ <!-- Hotseat --> <bool name="hotseat_transpose_layout_with_orientation">true</bool> - <!-- Memory debugging, including a memory dump icon --> - <bool name="debug_memory_enabled">false</bool> - <!-- Name of a subclass of com.android.launcher3.AppFilter used to filter the activities shown in the launcher. Can be empty. --> <string name="app_filter_class" translatable="false"></string> - <!-- Name of a subclass of com.android.launcher3.BuildInfo used to - get build information. Can be empty. --> - <string name="build_info_class" translatable="false"></string> - <!-- View ID to use for QSB widget --> <item type="id" name="qsb_widget" /> + <!-- View ID used by cell layout to jail its content --> + <item type="id" name="cell_layout_jail_id" /> + <!-- Accessibility actions --> <item type="id" name="action_remove" /> <item type="id" name="action_uninstall" /> diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 36721797e..4882df6e5 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -89,6 +89,7 @@ <dimen name="widget_section_icon_size">40dp</dimen> <dimen name="widget_section_vertical_padding">8dp</dimen> <dimen name="widget_section_horizontal_padding">16dp</dimen> + <dimen name="widget_section_indent">0dp</dimen> <dimen name="widget_row_padding">8dp</dimen> <dimen name="widget_row_divider">2dp</dimen> @@ -115,6 +116,8 @@ and drop targets like all-apps and folders --> <dimen name="drag_elevation">30dp</dimen> + <dimen name="drag_flingToDeleteMinVelocity">-1500dp</dimen> + <!-- Theme --> <dimen name="quantum_panel_outer_padding">4dp</dimen> diff --git a/res/values/strings.xml b/res/values/strings.xml index fefadef28..7947fbd4a 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -45,8 +45,6 @@ <!-- SafeMode widget error string --> <string name="safemode_widget_error">Widgets disabled in Safe mode</string> - <string name="toggle_weight_watcher">Show Mem</string> - <!-- Widgets --> <!-- Message to tell the user to press and hold on a widget to add it [CHAR_LIMIT=50] --> <string name="long_press_widget_to_add">Touch & hold to pick up a widget.</string> @@ -158,9 +156,6 @@ <!-- Folder name format --> <string name="folder_name_format">Folder: <xliff:g id="name" example="Games">%1$s</xliff:g></string> - <!-- Debug-only activity name. [DO NOT TRANSLATE] --> - <string name="debug_memory_activity">* HPROF</string> - <!-- Strings for the customization mode --> <!-- Text for widget add button --> <string name="widget_button_text">Widgets</string> diff --git a/src/com/android/launcher3/AnotherWindowDropTarget.java b/src/com/android/launcher3/AnotherWindowDropTarget.java new file mode 100644 index 000000000..eaac6be80 --- /dev/null +++ b/src/com/android/launcher3/AnotherWindowDropTarget.java @@ -0,0 +1,73 @@ +/* + * 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.launcher3; + +import android.content.Context; +import android.graphics.PointF; +import android.graphics.Rect; + +/** + * Drop target used when another window (i.e. another process) has accepted a global system drag. + * If the accepted item was a shortcut, we delete it from Launcher. + */ +public class AnotherWindowDropTarget implements DropTarget { + final Launcher mLauncher; + + public AnotherWindowDropTarget (Context context) { mLauncher = (Launcher) context; } + + @Override + public boolean isDropEnabled() { return true; } + + @Override + public void onDrop(DragObject dragObject) { + dragObject.deferDragViewCleanupPostAnimation = false; + LauncherModel.deleteItemFromDatabase(mLauncher, (ShortcutInfo) dragObject.dragInfo); + } + + @Override + public void onDragEnter(DragObject dragObject) {} + + @Override + public void onDragOver(DragObject dragObject) {} + + @Override + public void onDragExit(DragObject dragObject) {} + + @Override + public void onFlingToDelete(DragObject dragObject, PointF vec) {} + + @Override + public boolean acceptDrop(DragObject dragObject) { + return dragObject.dragInfo instanceof ShortcutInfo; + } + + @Override + public void prepareAccessibilityDrop() {} + + // These methods are implemented in Views + @Override + public void getHitRectRelativeToDragLayer(Rect outRect) {} + + @Override + public void getLocationInDragLayer(int[] loc) {} + + @Override + public int getLeft() { return 0; } + + @Override + public int getTop() { return 0; } +} diff --git a/src/com/android/launcher3/AppInfo.java b/src/com/android/launcher3/AppInfo.java index c95d5585a..7249c6406 100644 --- a/src/com/android/launcher3/AppInfo.java +++ b/src/com/android/launcher3/AppInfo.java @@ -28,13 +28,11 @@ import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.util.ComponentKey; import java.util.ArrayList; -import java.util.Arrays; /** * Represents an app in AllAppsView. */ public class AppInfo extends ItemInfo { - private static final String TAG = "Launcher3.AppInfo"; /** * The intent used to start the application. @@ -118,8 +116,7 @@ public class AppInfo extends ItemInfo { return "ApplicationInfo(title=" + title + " id=" + this.id + " type=" + this.itemType + " container=" + this.container + " screen=" + screenId + " cellX=" + cellX + " cellY=" + cellY - + " spanX=" + spanX + " spanY=" + spanY + " dropPos=" + Arrays.toString(dropPos) - + " user=" + user + ")"; + + " spanX=" + spanX + " spanY=" + spanY + " user=" + user + ")"; } /** @@ -128,7 +125,7 @@ public class AppInfo extends ItemInfo { public static void dumpApplicationInfoList(String tag, String label, ArrayList<AppInfo> list) { Log.d(tag, label + " size=" + list.size()); for (AppInfo info: list) { - Log.d(tag, " title=\"" + info.title + "\" iconBitmap=" + info.iconBitmap + Log.d(tag, " title=\"" + info.title + "\" iconBitmap=" + info.iconBitmap + " firstInstallTime=" + info.firstInstallTime + " componentName=" + info.componentName.getPackageName()); } diff --git a/src/com/android/launcher3/AppInfoDropTargetBar.java b/src/com/android/launcher3/AppInfoDropTargetBar.java new file mode 100644 index 000000000..31ff42a7f --- /dev/null +++ b/src/com/android/launcher3/AppInfoDropTargetBar.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3; + +import android.content.Context; +import android.util.AttributeSet; + +import com.android.launcher3.dragndrop.DragController; + +public class AppInfoDropTargetBar extends BaseDropTargetBar { + private ButtonDropTarget mAppInfoDropTarget; + + public AppInfoDropTargetBar(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public AppInfoDropTargetBar(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + // Get the individual components + mAppInfoDropTarget = (ButtonDropTarget) mDropTargetBar.findViewById(R.id.info_target_text); + + mAppInfoDropTarget.setDropTargetBar(this); + } + + @Override + public void setup(Launcher launcher, DragController dragController) { + dragController.addDragListener(this); + + dragController.addDragListener(mAppInfoDropTarget); + + dragController.addDropTarget(mAppInfoDropTarget); + + mAppInfoDropTarget.setLauncher(launcher); + } + + @Override + public void showDropTarget() { + animateDropTargetBarToAlpha(1f, DEFAULT_DRAG_FADE_DURATION); + } + + @Override + public void hideDropTarget() { + animateDropTargetBarToAlpha(0f, DEFAULT_DRAG_FADE_DURATION); + } + + private void animateDropTargetBarToAlpha(float alpha, int duration) { + animateViewAlpha(mDropTargetBarAnimator, mDropTargetBar, alpha,duration); + } + + @Override + public void enableAccessibleDrag(boolean enable) { + mAppInfoDropTarget.enableAccessibleDrag(enable); + } +} diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java index e6bf52531..6818929f7 100644 --- a/src/com/android/launcher3/AppWidgetResizeFrame.java +++ b/src/com/android/launcher3/AppWidgetResizeFrame.java @@ -1,5 +1,7 @@ package com.android.launcher3; +import com.android.launcher3.dragndrop.DragLayer; + import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; @@ -9,6 +11,7 @@ import android.appwidget.AppWidgetHostView; import android.appwidget.AppWidgetProviderInfo; import android.content.Context; import android.content.res.Resources; +import android.graphics.Point; import android.graphics.Rect; import android.view.Gravity; import android.widget.FrameLayout; @@ -19,7 +22,10 @@ public class AppWidgetResizeFrame extends FrameLayout { private static final float DIMMED_HANDLE_ALPHA = 0f; private static final float RESIZE_THRESHOLD = 0.66f; - private static Rect sTmpRect = new Rect(); + private static final Rect sTmpRect = new Rect(); + + // Represents the cell size on the grid in the two orientations. + private static Point[] sCellSize; private final Launcher mLauncher; private final LauncherAppWidgetHostView mWidgetView; @@ -341,28 +347,27 @@ public class AppWidgetResizeFrame extends FrameLayout { } public static Rect getWidgetSizeRanges(Launcher launcher, int spanX, int spanY, Rect rect) { + if (sCellSize == null) { + InvariantDeviceProfile inv = LauncherAppState.getInstance().getInvariantDeviceProfile(); + + // Initiate cell sizes. + sCellSize = new Point[2]; + sCellSize[0] = inv.landscapeProfile.getCellSize(); + sCellSize[1] = inv.portraitProfile.getCellSize(); + } + if (rect == null) { rect = new Rect(); } - Rect landMetrics = Workspace.getCellLayoutMetrics(launcher, CellLayout.LANDSCAPE); - Rect portMetrics = Workspace.getCellLayoutMetrics(launcher, CellLayout.PORTRAIT); final float density = launcher.getResources().getDisplayMetrics().density; // Compute landscape size - int cellWidth = landMetrics.left; - int cellHeight = landMetrics.top; - int widthGap = landMetrics.right; - int heightGap = landMetrics.bottom; - int landWidth = (int) ((spanX * cellWidth + (spanX - 1) * widthGap) / density); - int landHeight = (int) ((spanY * cellHeight + (spanY - 1) * heightGap) / density); + int landWidth = (int) ((spanX * sCellSize[0].x) / density); + int landHeight = (int) ((spanY * sCellSize[0].y) / density); // Compute portrait size - cellWidth = portMetrics.left; - cellHeight = portMetrics.top; - widthGap = portMetrics.right; - heightGap = portMetrics.bottom; - int portWidth = (int) ((spanX * cellWidth + (spanX - 1) * widthGap) / density); - int portHeight = (int) ((spanY * cellHeight + (spanY - 1) * heightGap) / density); + int portWidth = (int) ((spanX * sCellSize[1].x) / density); + int portHeight = (int) ((spanY * sCellSize[1].y) / density); rect.set(portWidth, landHeight, landWidth, portHeight); return rect; } @@ -441,10 +446,10 @@ public class AppWidgetResizeFrame extends FrameLayout { PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", lp.y, newY); ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(lp, this, width, height, x, y); - ObjectAnimator leftOa = LauncherAnimUtils.ofFloat(mLeftHandle, "alpha", 1.0f); - ObjectAnimator rightOa = LauncherAnimUtils.ofFloat(mRightHandle, "alpha", 1.0f); - ObjectAnimator topOa = LauncherAnimUtils.ofFloat(mTopHandle, "alpha", 1.0f); - ObjectAnimator bottomOa = LauncherAnimUtils.ofFloat(mBottomHandle, "alpha", 1.0f); + ObjectAnimator leftOa = LauncherAnimUtils.ofFloat(mLeftHandle, ALPHA, 1.0f); + ObjectAnimator rightOa = LauncherAnimUtils.ofFloat(mRightHandle, ALPHA, 1.0f); + ObjectAnimator topOa = LauncherAnimUtils.ofFloat(mTopHandle, ALPHA, 1.0f); + ObjectAnimator bottomOa = LauncherAnimUtils.ofFloat(mBottomHandle, ALPHA, 1.0f); oa.addUpdateListener(new AnimatorUpdateListener() { public void onAnimationUpdate(ValueAnimator animation) { requestLayout(); diff --git a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java index b1d51ece0..10fdd8746 100644 --- a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java +++ b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java @@ -13,7 +13,6 @@ import android.os.AsyncTask; import android.util.Log; import com.android.launcher3.LauncherSettings.Favorites; -import com.android.launcher3.compat.AppWidgetManagerCompat; import java.util.ArrayList; import java.util.List; diff --git a/src/com/android/launcher3/BaseContainerView.java b/src/com/android/launcher3/BaseContainerView.java index c11824054..e0946ea14 100644 --- a/src/com/android/launcher3/BaseContainerView.java +++ b/src/com/android/launcher3/BaseContainerView.java @@ -22,6 +22,8 @@ import android.util.AttributeSet; import android.util.Log; import android.widget.LinearLayout; +import com.android.launcher3.config.ProviderConfig; + /** * A base container view, which supports resizing. */ @@ -71,7 +73,7 @@ public abstract class BaseContainerView extends LinearLayout implements Insettab * Sets the search bar bounds for this container view to match. */ final public void setSearchBarBounds(Rect bounds) { - if (LauncherAppState.isDogfoodBuild() && !isValidSearchBarBounds(bounds)) { + if (ProviderConfig.IS_DOGFOOD_BUILD && !isValidSearchBarBounds(bounds)) { Log.e(TAG, "Invalid search bar bounds: " + bounds); } diff --git a/src/com/android/launcher3/BaseDropTargetBar.java b/src/com/android/launcher3/BaseDropTargetBar.java new file mode 100644 index 000000000..f478a3530 --- /dev/null +++ b/src/com/android/launcher3/BaseDropTargetBar.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.view.animation.AccelerateInterpolator; +import android.widget.FrameLayout; + +import com.android.launcher3.dragndrop.DragController; + +/** + * Base class for drop target bars (where you can drop apps to do actions such as uninstall). + */ +public abstract class BaseDropTargetBar extends FrameLayout implements DragController.DragListener { + protected static final int DEFAULT_DRAG_FADE_DURATION = 175; + + protected View mDropTargetBar; + + protected LauncherViewPropertyAnimator mDropTargetBarAnimator; + protected static final AccelerateInterpolator sAccelerateInterpolator = + new AccelerateInterpolator(); + protected boolean mAccessibilityEnabled = false; + + protected boolean mDeferOnDragEnd; + + public BaseDropTargetBar(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public BaseDropTargetBar(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + mDropTargetBar = findViewById(R.id.drag_target_bar); + + // Create the various fade animations + mDropTargetBar.setAlpha(0f); + mDropTargetBarAnimator = new LauncherViewPropertyAnimator(mDropTargetBar); + mDropTargetBarAnimator.setInterpolator(sAccelerateInterpolator); + mDropTargetBarAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + // Ensure that the view is visible for the animation + mDropTargetBar.setVisibility(View.VISIBLE); + } + + @Override + public void onAnimationEnd(Animator animation) { + if (mDropTargetBar != null) { + AlphaUpdateListener.updateVisibility(mDropTargetBar, mAccessibilityEnabled); + } + } + }); + } + + + /** + * Convenience method to animate the alpha of a view using hardware layers. + */ + protected void animateViewAlpha(LauncherViewPropertyAnimator animator, View v, float alpha, + int duration) { + if (v == null) { + return; + } + + animator.cancel(); + if (Float.compare(v.getAlpha(), alpha) != 0) { + if (duration > 0) { + animator.alpha(alpha).withLayer().setDuration(duration).start(); + } else { + v.setAlpha(alpha); + AlphaUpdateListener.updateVisibility(v, mAccessibilityEnabled); + } + } + } + + /* + * DragController.DragListener implementation + */ + @Override + public void onDragStart(DragSource source, ItemInfo info, int dragAction) { + showDropTarget(); + } + + /** + * This is called to defer hiding the delete drop target until the drop animation has completed, + * instead of hiding immediately when the drag has ended. + */ + protected void deferOnDragEnd() { + mDeferOnDragEnd = true; + } + + @Override + public void onDragEnd() { + if (!mDeferOnDragEnd) { + hideDropTarget(); + } else { + mDeferOnDragEnd = false; + } + } + + public abstract void showDropTarget(); + + public abstract void hideDropTarget(); + + public abstract void enableAccessibleDrag(boolean enable); + + public abstract void setup(Launcher launcher, DragController dragController); +} diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index 507087824..c8af600ba 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -144,7 +144,7 @@ public class BubbleTextView extends TextView } mLongPressHelper = new CheckLongPressHelper(this); - mStylusEventHelper = new StylusEventHelper(this); + mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this); mOutlineHelper = HolographicOutlineHelper.obtain(getContext()); if (mCustomShadowsEnabled) { @@ -270,7 +270,7 @@ public class BubbleTextView extends TextView boolean result = super.onTouchEvent(event); // Check for a stylus button press, if it occurs cancel any long press checks. - if (mStylusEventHelper.checkAndPerformStylusEvent(event)) { + if (mStylusEventHelper.onMotionEvent(event)) { mLongPressHelper.cancelLongPress(); result = true; } diff --git a/src/com/android/launcher3/BuildInfo.java b/src/com/android/launcher3/BuildInfo.java index b49ee0d9b..1392d7a43 100644 --- a/src/com/android/launcher3/BuildInfo.java +++ b/src/com/android/launcher3/BuildInfo.java @@ -1,32 +1,9 @@ package com.android.launcher3; -import android.text.TextUtils; -import android.util.Log; - +// TODO: Remove this class once all its references are gone. public class BuildInfo { - private static final boolean DBG = false; - private static final String TAG = "BuildInfo"; public boolean isDogfoodBuild() { return false; } - - public static BuildInfo loadByName(String className) { - if (TextUtils.isEmpty(className)) return new BuildInfo(); - - if (DBG) Log.d(TAG, "Loading BuildInfo: " + className); - try { - Class<?> cls = Class.forName(className); - return (BuildInfo) cls.newInstance(); - } catch (ClassNotFoundException e) { - Log.e(TAG, "Bad BuildInfo class", e); - } catch (InstantiationException e) { - Log.e(TAG, "Bad BuildInfo class", e); - } catch (IllegalAccessException e) { - Log.e(TAG, "Bad BuildInfo class", e); - } catch (ClassCastException e) { - Log.e(TAG, "Bad BuildInfo class", e); - } - return new BuildInfo(); - } } diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java index 2baa6d8ee..703de5326 100644 --- a/src/com/android/launcher3/ButtonDropTarget.java +++ b/src/com/android/launcher3/ButtonDropTarget.java @@ -38,6 +38,9 @@ import android.view.animation.DecelerateInterpolator; import android.view.animation.LinearInterpolator; import android.widget.TextView; +import com.android.launcher3.dragndrop.DragController; +import com.android.launcher3.dragndrop.DragLayer; +import com.android.launcher3.dragndrop.DragView; import com.android.launcher3.util.Thunk; /** @@ -46,11 +49,11 @@ import com.android.launcher3.util.Thunk; public abstract class ButtonDropTarget extends TextView implements DropTarget, DragController.DragListener, OnClickListener { - private static int DRAG_VIEW_DROP_DURATION = 285; + private static final int DRAG_VIEW_DROP_DURATION = 285; protected Launcher mLauncher; private int mBottomDragPadding; - protected SearchDropTargetBar mSearchDropTargetBar; + protected BaseDropTargetBar mDropTargetBar; /** Whether this drop target is active for the current drag */ protected boolean mActive; @@ -103,8 +106,8 @@ public abstract class ButtonDropTarget extends TextView mLauncher = launcher; } - public void setSearchDropTargetBar(SearchDropTargetBar searchDropTargetBar) { - mSearchDropTargetBar = searchDropTargetBar; + public void setDropTargetBar(BaseDropTargetBar dropTargetBar) { + mDropTargetBar = dropTargetBar; } @Override @@ -184,8 +187,8 @@ public abstract class ButtonDropTarget extends TextView } } - @Override - public final void onDragStart(DragSource source, Object info, int dragAction) { + @Override + public final void onDragStart(DragSource source, ItemInfo info, int dragAction) { mActive = supportsDrop(source, info); mDrawable.setColorFilter(null); if (mCurrentColorAnim != null) { @@ -201,7 +204,7 @@ public abstract class ButtonDropTarget extends TextView return supportsDrop(dragObject.dragSource, dragObject.dragInfo); } - protected abstract boolean supportsDrop(DragSource source, Object info); + protected abstract boolean supportsDrop(DragSource source, ItemInfo info); @Override public boolean isDropEnabled() { @@ -227,13 +230,13 @@ public abstract class ButtonDropTarget extends TextView final Rect to = getIconRect(d.dragView.getMeasuredWidth(), d.dragView.getMeasuredHeight(), width, height); final float scale = (float) to.width() / from.width(); - mSearchDropTargetBar.deferOnDragEnd(); + mDropTargetBar.deferOnDragEnd(); Runnable onAnimationEndRunnable = new Runnable() { @Override public void run() { completeDrop(d); - mSearchDropTargetBar.onDragEnd(); + mDropTargetBar.onDragEnd(); mLauncher.exitSpringLoadedDragModeDelayed(true, 0, null); } }; diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java index d48873762..b8b41443e 100644 --- a/src/com/android/launcher3/CellLayout.java +++ b/src/com/android/launcher3/CellLayout.java @@ -18,14 +18,12 @@ package com.android.launcher3; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; import android.annotation.TargetApi; import android.content.Context; import android.content.res.Resources; -import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; @@ -52,9 +50,12 @@ import android.widget.Toast; import com.android.launcher3.BubbleTextView.BubbleTextShadowHandler; import com.android.launcher3.FolderIcon.FolderRingAnimator; +import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate; import com.android.launcher3.accessibility.FolderAccessibilityHelper; import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper; +import com.android.launcher3.config.ProviderConfig; +import com.android.launcher3.util.ParcelableSparseArray; import com.android.launcher3.util.Thunk; import java.util.ArrayList; @@ -86,6 +87,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { private int mMaxGap; private boolean mDropPending = false; private boolean mIsDragTarget = true; + private boolean mJailContent = true; // These are temporary variables to prevent having to allocate a new object just to // return an (x, y) value from helper functions. Do NOT use them to maintain other state. @@ -189,7 +191,6 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { mLauncher = (Launcher) context; DeviceProfile grid = mLauncher.getDeviceProfile(); - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0); mCellWidth = mCellHeight = -1; mFixedCellWidth = mFixedCellHeight = -1; @@ -203,10 +204,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { mPreviousReorderDirection[0] = INVALID_DIRECTION; mPreviousReorderDirection[1] = INVALID_DIRECTION; - a.recycle(); - setAlwaysDrawnWithCacheEnabled(false); - final Resources res = getResources(); mHotseatScale = (float) grid.hotseatIconSizePx / grid.iconSizePx; @@ -278,7 +276,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap, mCountX, mCountY); - mStylusEventHelper = new StylusEventHelper(this); + mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this); mTouchFeedbackView = new ClickShadowView(context); addView(mTouchFeedbackView); @@ -340,7 +338,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { // enabled to allow rearranging the different home screens. So check what mode // the workspace is in, and only perform stylus button presses while in overview mode. if (mLauncher.mWorkspace.isInOverviewMode() - && mStylusEventHelper.checkAndPerformStylusEvent(ev)) { + && mStylusEventHelper.onMotionEvent(ev)) { return true; } return handled; @@ -406,7 +404,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { mIsDragTarget = false; } - boolean isDragTarget() { + public boolean isDragTarget() { return mIsDragTarget; } @@ -426,7 +424,33 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { } } - boolean getIsDragOverlapping() { + public void disableJailContent() { + mJailContent = false; + } + + @Override + protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) { + if (mJailContent) { + ParcelableSparseArray jail = getJailedArray(container); + super.dispatchSaveInstanceState(jail); + container.put(R.id.cell_layout_jail_id, jail); + } else { + super.dispatchSaveInstanceState(container); + } + } + + @Override + protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) { + super.dispatchRestoreInstanceState(mJailContent ? getJailedArray(container) : container); + } + + private ParcelableSparseArray getJailedArray(SparseArray<Parcelable> container) { + final Parcelable parcelable = container.get(R.id.cell_layout_jail_id); + return parcelable instanceof ParcelableSparseArray ? + (ParcelableSparseArray) parcelable : new ParcelableSparseArray(); + } + + public boolean getIsDragOverlapping() { return mIsDragOverlapping; } @@ -449,12 +473,9 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { for (int i = 0; i < mDragOutlines.length; i++) { final float alpha = mDragOutlineAlphas[i]; if (alpha > 0) { - final Rect r = mDragOutlines[i]; - mTempRect.set(r); - Utilities.scaleRectAboutCenter(mTempRect, getChildrenScale()); final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag(); paint.setAlpha((int)(alpha + .5f)); - canvas.drawBitmap(b, null, mTempRect, paint); + canvas.drawBitmap(b, null, mDragOutlines[i], paint); } } @@ -569,7 +590,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { try { dispatchRestoreInstanceState(states); } catch (IllegalArgumentException ex) { - if (LauncherAppState.isDogfoodBuild()) { + if (ProviderConfig.IS_DOGFOOD_BUILD) { throw ex; } // Mismatched viewId / viewType preventing restore. Skip restore on production builds. @@ -1032,53 +1053,57 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { if (cellX != oldDragCellX || cellY != oldDragCellY) { mDragCell[0] = cellX; mDragCell[1] = cellY; - // Find the top left corner of the rect the object will occupy - final int[] topLeft = mTmpPoint; - cellToPoint(cellX, cellY, topLeft); - - int left = topLeft[0]; - int top = topLeft[1]; - - if (v != null && dragOffset == null) { - // When drawing the drag outline, it did not account for margin offsets - // added by the view's parent. - MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams(); - left += lp.leftMargin; - top += lp.topMargin; - - // Offsets due to the size difference between the View and the dragOutline. - // There is a size difference to account for the outer blur, which may lie - // outside the bounds of the view. - top += (v.getHeight() - dragOutline.getHeight()) / 2; - // We center about the x axis - left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap) - - dragOutline.getWidth()) / 2; - } else { - if (dragOffset != null && dragRegion != null) { - // Center the drag region *horizontally* in the cell and apply a drag - // outline offset - left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap) - - dragRegion.width()) / 2; - int cHeight = getShortcutsAndWidgets().getCellContentHeight(); - int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f)); - top += dragOffset.y + cellPaddingY; - } else { - // Center the drag outline in the cell - left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap) - - dragOutline.getWidth()) / 2; - top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap) - - dragOutline.getHeight()) / 2; - } - } + final int oldIndex = mDragOutlineCurrent; mDragOutlineAnims[oldIndex].animateOut(); mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length; Rect r = mDragOutlines[mDragOutlineCurrent]; - r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight()); + if (resize) { cellToRect(cellX, cellY, spanX, spanY, r); + } else { + // Find the top left corner of the rect the object will occupy + final int[] topLeft = mTmpPoint; + cellToPoint(cellX, cellY, topLeft); + + int left = topLeft[0]; + int top = topLeft[1]; + + if (v != null && dragOffset == null) { + // When drawing the drag outline, it did not account for margin offsets + // added by the view's parent. + MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams(); + left += lp.leftMargin; + top += lp.topMargin; + + // Offsets due to the size difference between the View and the dragOutline. + // There is a size difference to account for the outer blur, which may lie + // outside the bounds of the view. + top += (v.getHeight() - dragOutline.getHeight()) / 2; + // We center about the x axis + left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap) + - dragOutline.getWidth()) / 2; + } else { + if (dragOffset != null && dragRegion != null) { + // Center the drag region *horizontally* in the cell and apply a drag + // outline offset + left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap) + - dragRegion.width()) / 2; + int cHeight = getShortcutsAndWidgets().getCellContentHeight(); + int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f)); + top += dragOffset.y + cellPaddingY; + } else { + // Center the drag outline in the cell + left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap) + - dragOutline.getWidth()) / 2; + top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap) + - dragOutline.getHeight()) / 2; + } + } + r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight()); } + Utilities.scaleRectAboutCenter(r, getChildrenScale()); mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline); mDragOutlineAnims[mDragOutlineCurrent].animateIn(); } @@ -1096,23 +1121,6 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { * * @param pixelX The X location at which you want to search for a vacant area. * @param pixelY The Y location at which you want to search for a vacant area. - * @param spanX Horizontal span of the object. - * @param spanY Vertical span of the object. - * @param result Array in which to place the result, or null (in which case a new array will - * be allocated) - * @return The X, Y cell of a vacant area that can contain this object, - * nearest the requested location. - */ - int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY, int[] result) { - return findNearestVacantArea(pixelX, pixelY, spanX, spanY, spanX, spanY, result, null); - } - - /** - * Find a vacant area that will fit the given bounds nearest the requested - * cell location. Uses Euclidean distance to score multiple vacant areas. - * - * @param pixelX The X location at which you want to search for a vacant area. - * @param pixelY The Y location at which you want to search for a vacant area. * @param minSpanX The minimum horizontal span required * @param minSpanY The minimum vertical span required * @param spanX Horizontal span of the object. @@ -2187,17 +2195,14 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { a.cancel(); } - AnimatorSet s = LauncherAnimUtils.createAnimatorSet(); - a = s; - s.playTogether( - LauncherAnimUtils.ofFloat(child, "scaleX", getChildrenScale()), - LauncherAnimUtils.ofFloat(child, "scaleY", getChildrenScale()), - LauncherAnimUtils.ofFloat(child, "translationX", 0f), - LauncherAnimUtils.ofFloat(child, "translationY", 0f) - ); - s.setDuration(REORDER_ANIMATION_DURATION); - s.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f)); - s.start(); + a = new LauncherViewPropertyAnimator(child) + .scaleX(getChildrenScale()) + .scaleY(getChildrenScale()) + .translationX(0) + .translationY(0) + .setDuration(REORDER_ANIMATION_DURATION); + a.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f)); + a.start(); } } @@ -2214,6 +2219,15 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { mOccupied[i][j] = mTmpOccupied[i][j]; } } + + long screenId = mLauncher.getWorkspace().getIdForScreen(this); + int container = Favorites.CONTAINER_DESKTOP; + + if (mLauncher.isHotseatLayout(this)) { + screenId = -1; + container = Favorites.CONTAINER_HOTSEAT; + } + int childCount = mShortcutsAndWidgets.getChildCount(); for (int i = 0; i < childCount; i++) { View child = mShortcutsAndWidgets.getChildAt(i); @@ -2222,17 +2236,21 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { // We do a null check here because the item info can be null in the case of the // AllApps button in the hotseat. if (info != null) { - if (info.cellX != lp.tmpCellX || info.cellY != lp.tmpCellY || - info.spanX != lp.cellHSpan || info.spanY != lp.cellVSpan) { - info.requiresDbUpdate = true; - } + final boolean requiresDbUpdate = (info.cellX != lp.tmpCellX + || info.cellY != lp.tmpCellY || info.spanX != lp.cellHSpan + || info.spanY != lp.cellVSpan); + info.cellX = lp.cellX = lp.tmpCellX; info.cellY = lp.cellY = lp.tmpCellY; info.spanX = lp.cellHSpan; info.spanY = lp.cellVSpan; + + if (requiresDbUpdate) { + LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId, + info.cellX, info.cellY, info.spanX, info.spanY); + } } } - mLauncher.getWorkspace().updateItemLocationsInDatabase(this); } private void setUseTempCoords(boolean useTempCoords) { @@ -2820,10 +2838,10 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { // X coordinate of the view in the layout. @ViewDebug.ExportedProperty - int x; + public int x; // Y coordinate of the view in the layout. @ViewDebug.ExportedProperty - int y; + public int y; boolean dropped; @@ -2949,6 +2967,26 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { return Utilities.findVacantCell(outXY, spanX, spanY, mCountX, mCountY, mOccupied); } + /** + * Returns whether an item can be placed in this CellLayout (after rearranging and/or resizing + * if necessary). + */ + public boolean hasReorderSolution(ItemInfo itemInfo) { + int[] cellPoint = new int[2]; + // Check for a solution starting at every cell. + for (int cellX = 0; cellX < getCountX(); cellX++) { + for (int cellY = 0; cellY < getCountY(); cellY++) { + cellToPoint(cellX, cellY, cellPoint); + if (findReorderSolution(cellPoint[0], cellPoint[1], itemInfo.minSpanX, + itemInfo.minSpanY, itemInfo.spanX, itemInfo.spanY, mDirectionVector, null, + true, new ItemConfiguration()).isSolution) { + return true; + } + } + } + return false; + } + public boolean isRegionVacant(int x, int y, int spanX, int spanY) { int x2 = x + spanX - 1; int y2 = y + spanY - 1; diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java index c3eea2f08..76e14f51c 100644 --- a/src/com/android/launcher3/DeleteDropTarget.java +++ b/src/com/android/launcher3/DeleteDropTarget.java @@ -19,11 +19,11 @@ package com.android.launcher3; import android.animation.TimeInterpolator; import android.content.Context; import android.graphics.PointF; -import android.os.AsyncTask; import android.util.AttributeSet; import android.view.View; import android.view.animation.AnimationUtils; +import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.util.FlingAnimation; import com.android.launcher3.util.Thunk; @@ -46,20 +46,20 @@ public class DeleteDropTarget extends ButtonDropTarget { setDrawable(R.drawable.ic_remove_launcher); } - public static boolean supportsDrop(Object info) { + public static boolean supportsDrop(ItemInfo info) { return (info instanceof ShortcutInfo) || (info instanceof LauncherAppWidgetInfo) || (info instanceof FolderInfo); } @Override - protected boolean supportsDrop(DragSource source, Object info) { + protected boolean supportsDrop(DragSource source, ItemInfo info) { return source.supportsDeleteDropTarget() && supportsDrop(info); } @Override @Thunk void completeDrop(DragObject d) { - ItemInfo item = (ItemInfo) d.dragInfo; + ItemInfo item = d.dragInfo; if ((d.dragSource instanceof Workspace) || (d.dragSource instanceof Folder)) { removeWorkspaceOrFolderItem(mLauncher, item, null); } @@ -82,7 +82,6 @@ public class DeleteDropTarget extends ButtonDropTarget { public void onFlingToDelete(final DragObject d, PointF vel) { // Don't highlight the icon as it's animating d.dragView.setColor(0); - d.dragView.updateInitialScaleToCurrentScale(); final DragLayer dragLayer = mLauncher.getDragLayer(); FlingAnimation fling = new FlingAnimation(d, vel, diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index 774594fe2..69ef82683 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -280,6 +280,18 @@ public class DeviceProfile { return bounds; } + public Point getCellSize() { + Point result = new Point(); + // Since we are only concerned with the overall padding, layout direction does + // not matter. + Rect padding = getWorkspacePadding(false /* isLayoutRtl */ ); + result.x = calculateCellWidth(availableWidthPx - padding.left - padding.right, + inv.numColumns); + result.y = calculateCellHeight(availableHeightPx - padding.top - padding.bottom, + inv.numRows); + return result; + } + /** Returns the workspace padding in the specified orientation */ Rect getWorkspacePadding(boolean isLayoutRtl) { Rect searchBarBounds = getSearchBarBounds(isLayoutRtl); @@ -339,13 +351,13 @@ public class DeviceProfile { } // The rect returned will be extended to below the system ui that covers the workspace - Rect getHotseatRect() { + public boolean isInHotseatRect(int x, int y) { if (isVerticalBarLayout()) { - return new Rect(availableWidthPx - hotseatBarHeightPx, 0, - Integer.MAX_VALUE, availableHeightPx); + return (x >= (availableWidthPx - hotseatBarHeightPx)) + && (y >= 0) && (y <= availableHeightPx); } else { - return new Rect(0, availableHeightPx - hotseatBarHeightPx, - availableWidthPx, Integer.MAX_VALUE); + return (x >= 0) && (x <= availableWidthPx) + && (y >= (availableHeightPx - hotseatBarHeightPx)); } } @@ -360,7 +372,7 @@ public class DeviceProfile { * When {@code true}, hotseat is on the bottom row when in landscape mode. * If {@code false}, hotseat is on the right column when in landscape mode. */ - boolean isVerticalBarLayout() { + public boolean isVerticalBarLayout() { return isLandscape && transposeLayoutWithOrientation; } @@ -385,29 +397,15 @@ public class DeviceProfile { // Layout the search bar space View searchBar = launcher.getSearchDropTargetBar(); - lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams(); - if (hasVerticalBarLayout) { - // Vertical search bar space -- The search bar is fixed in the layout to be on the left - // of the screen regardless of RTL - lp.gravity = Gravity.LEFT; - lp.width = searchBarSpaceHeightPx; - - LinearLayout targets = (LinearLayout) searchBar.findViewById(R.id.drag_target_bar); - targets.setOrientation(LinearLayout.VERTICAL); - FrameLayout.LayoutParams targetsLp = (FrameLayout.LayoutParams) targets.getLayoutParams(); - targetsLp.gravity = Gravity.TOP; - targetsLp.height = LayoutParams.WRAP_CONTENT; - - } else { - // Horizontal search bar space - lp.gravity = Gravity.TOP; - lp.height = searchBarSpaceHeightPx; - - LinearLayout targets = (LinearLayout) searchBar.findViewById(R.id.drag_target_bar); - targets.getLayoutParams().width = searchBarSpaceWidthPx; - } + lp = getDropTargetBarLayoutParams(hasVerticalBarLayout, searchBar, Gravity.TOP); searchBar.setLayoutParams(lp); + // Layout the app info bar space + View appInfoBar = launcher.getAppInfoDropTargetBar(); + lp = getDropTargetBarLayoutParams(hasVerticalBarLayout, appInfoBar, Gravity.BOTTOM); + lp.bottomMargin = hotseatBarHeightPx; + appInfoBar.setLayoutParams(lp); + // Layout the workspace PagedView workspace = (PagedView) launcher.findViewById(R.id.workspace); lp = (FrameLayout.LayoutParams) workspace.getLayoutParams(); @@ -466,7 +464,6 @@ public class DeviceProfile { // Layout the Overview Mode ViewGroup overviewMode = launcher.getOverviewPanel(); if (overviewMode != null) { - int overviewButtonBarHeight = getOverviewModeButtonBarHeight(); lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams(); lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM; @@ -475,7 +472,7 @@ public class DeviceProfile { int maxWidth = totalItemWidth + (visibleChildCount-1) * overviewModeBarSpacerWidthPx; lp.width = Math.min(availableWidthPx, maxWidth); - lp.height = overviewButtonBarHeight; + lp.height = getOverviewModeButtonBarHeight(); overviewMode.setLayoutParams(lp); if (lp.width > totalItemWidth && visibleChildCount > 1) { @@ -504,6 +501,31 @@ public class DeviceProfile { } } + private FrameLayout.LayoutParams getDropTargetBarLayoutParams(boolean hasVerticalBarLayout, + View dropTargetBar, int verticalGravity) { + FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) dropTargetBar.getLayoutParams(); + if (hasVerticalBarLayout) { + // Vertical drop target bar space -- The drop target bar is fixed in the layout to be on + // the left of the screen regardless of RTL + lp.gravity = Gravity.LEFT; + lp.width = searchBarSpaceHeightPx; + + LinearLayout targets = (LinearLayout) dropTargetBar.findViewById(R.id.drag_target_bar); + targets.setOrientation(LinearLayout.VERTICAL); + FrameLayout.LayoutParams targetsLp = (FrameLayout.LayoutParams) targets.getLayoutParams(); + targetsLp.gravity = verticalGravity; + targetsLp.height = LayoutParams.WRAP_CONTENT; + } else { + // Horizontal drop target bar space + lp.gravity = verticalGravity; + lp.height = searchBarSpaceHeightPx; + + LinearLayout targets = (LinearLayout) dropTargetBar.findViewById(R.id.drag_target_bar); + targets.getLayoutParams().width = searchBarSpaceWidthPx; + } + return lp; + } + private int getCurrentWidth() { return isLandscape ? Math.max(widthPx, heightPx) diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java index c8fac5466..592cd3277 100644 --- a/src/com/android/launcher3/DropTarget.java +++ b/src/com/android/launcher3/DropTarget.java @@ -16,6 +16,8 @@ package com.android.launcher3; +import com.android.launcher3.dragndrop.DragView; + import android.graphics.PointF; import android.graphics.Rect; @@ -47,7 +49,7 @@ public interface DropTarget { public DragView dragView = null; /** The data associated with the object being dragged */ - public Object dragInfo = null; + public ItemInfo dragInfo = null; /** Where the drag originated */ public DragSource dragSource = null; diff --git a/src/com/android/launcher3/ExtendedEditText.java b/src/com/android/launcher3/ExtendedEditText.java index c7b64ec7d..bf4551b26 100644 --- a/src/com/android/launcher3/ExtendedEditText.java +++ b/src/com/android/launcher3/ExtendedEditText.java @@ -17,6 +17,7 @@ package com.android.launcher3; import android.content.Context; import android.util.AttributeSet; +import android.view.DragEvent; import android.view.KeyEvent; import android.widget.EditText; @@ -62,4 +63,10 @@ public class ExtendedEditText extends EditText { } return super.onKeyPreIme(keyCode, event); } + + @Override + public boolean onDragEvent(DragEvent event) { + // We don't want this view to interfere with Launcher own drag and drop. + return false; + } } diff --git a/src/com/android/launcher3/FocusHelper.java b/src/com/android/launcher3/FocusHelper.java index 5c91cc0f0..be4d04f93 100644 --- a/src/com/android/launcher3/FocusHelper.java +++ b/src/com/android/launcher3/FocusHelper.java @@ -22,6 +22,7 @@ import android.view.SoundEffectConstants; import android.view.View; import android.view.ViewGroup; +import com.android.launcher3.config.ProviderConfig; import com.android.launcher3.util.FocusLogic; import com.android.launcher3.util.Thunk; @@ -73,7 +74,7 @@ public class FocusHelper { } if (!(v.getParent() instanceof ShortcutAndWidgetContainer)) { - if (LauncherAppState.isDogfoodBuild()) { + if (ProviderConfig.IS_DOGFOOD_BUILD) { throw new IllegalStateException("Parent of the focused item is not supported."); } else { return false; diff --git a/src/com/android/launcher3/FocusIndicatorView.java b/src/com/android/launcher3/FocusIndicatorView.java index ecf93e4b3..2337f6eed 100644 --- a/src/com/android/launcher3/FocusIndicatorView.java +++ b/src/com/android/launcher3/FocusIndicatorView.java @@ -16,7 +16,7 @@ package com.android.launcher3; -import android.animation.ObjectAnimator; +import android.animation.Animator; import android.animation.PropertyValuesHolder; import android.content.Context; import android.graphics.Canvas; @@ -36,7 +36,7 @@ public class FocusIndicatorView extends View implements View.OnFocusChangeListen private final int[] mIndicatorPos = new int[2]; private final int[] mTargetViewPos = new int[2]; - private ObjectAnimator mCurrentAnimation; + private Animator mCurrentAnimation; private ViewAnimState mTargetState; private View mLastFocusedView; @@ -98,12 +98,12 @@ public class FocusIndicatorView extends View implements View.OnFocusChangeListen if (getAlpha() > MIN_VISIBLE_ALPHA) { mTargetState = nextState; - mCurrentAnimation = LauncherAnimUtils.ofPropertyValuesHolder(this, - PropertyValuesHolder.ofFloat(View.ALPHA, 1), - PropertyValuesHolder.ofFloat(View.TRANSLATION_X, mTargetState.x), - PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, mTargetState.y), - PropertyValuesHolder.ofFloat(View.SCALE_X, mTargetState.scaleX), - PropertyValuesHolder.ofFloat(View.SCALE_Y, mTargetState.scaleY)); + mCurrentAnimation = new LauncherViewPropertyAnimator(this) + .alpha(1) + .translationX(mTargetState.x) + .translationY(mTargetState.y) + .scaleX(mTargetState.scaleX) + .scaleY(mTargetState.scaleY); } else { applyState(nextState); mCurrentAnimation = LauncherAnimUtils.ofPropertyValuesHolder(this, diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java index d4e5d6d05..ff65d3098 100644 --- a/src/com/android/launcher3/Folder.java +++ b/src/com/android/launcher3/Folder.java @@ -36,6 +36,7 @@ import android.text.Spannable; import android.util.AttributeSet; import android.util.Log; import android.view.ActionMode; +import android.view.FocusFinder; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; @@ -52,11 +53,13 @@ import android.widget.LinearLayout; import android.widget.TextView; import com.android.launcher3.CellLayout.CellInfo; -import com.android.launcher3.DragController.DragListener; import com.android.launcher3.FolderInfo.FolderListener; import com.android.launcher3.UninstallDropTarget.UninstallSource; import com.android.launcher3.Workspace.ItemOperator; import com.android.launcher3.accessibility.LauncherAccessibilityDelegate.AccessibilityDragSource; +import com.android.launcher3.dragndrop.DragController; +import com.android.launcher3.dragndrop.DragController.DragListener; +import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.util.Thunk; import com.android.launcher3.util.UiThreadCircularReveal; @@ -316,7 +319,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList if (commit) { sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, - String.format(getContext().getString(R.string.folder_renamed), newTitle)); + getContext().getString(R.string.folder_renamed, newTitle)); } // In order to clear the focus from the text field, we set the focus on ourself. This // ensures that every time the field is clicked, focus is gained, giving reliable behavior. @@ -356,11 +359,26 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList } @Override + protected void onAttachedToWindow() { + // requestFocus() causes the focus onto the folder itself, which doesn't cause visual + // effect but the next arrow key can start the keyboard focus inside of the folder, not + // the folder itself. + requestFocus(); + super.onAttachedToWindow(); + } + + @Override public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { // When the folder gets focus, we don't want to announce the list of items. return true; } + @Override + public View focusSearch(int direction) { + // When the folder is focused, further focus search should be within the folder contents. + return FocusFinder.getInstance().findNextFocus(this, null, direction); + } + /** * @return the FolderInfo object associated with this folder */ @@ -457,11 +475,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList positionAndSizeAsIcon(); centerAboutIcon(); - PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1); - PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f); - PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f); - final ObjectAnimator oa = - LauncherAnimUtils.ofPropertyValuesHolder(this, alpha, scaleX, scaleY); + final ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(this, 1, 1, 1); oa.setDuration(mExpandDuration); openFolderAnim = oa; @@ -484,8 +498,8 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList float transY = - 0.075f * (height / 2 - getPivotY()); setTranslationX(transX); setTranslationY(transY); - PropertyValuesHolder tx = PropertyValuesHolder.ofFloat("translationX", transX, 0); - PropertyValuesHolder ty = PropertyValuesHolder.ofFloat("translationY", transY, 0); + PropertyValuesHolder tx = PropertyValuesHolder.ofFloat(TRANSLATION_X, transX, 0); + PropertyValuesHolder ty = PropertyValuesHolder.ofFloat(TRANSLATION_Y, transY, 0); Animator drift = ObjectAnimator.ofPropertyValuesHolder(this, tx, ty); drift.setDuration(mMaterialExpandDuration); @@ -564,6 +578,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList final boolean updateAnimationFlag = !mDragInProgress; openFolderAnim.addListener(new AnimatorListenerAdapter() { + @SuppressLint("InlinedApi") @Override public void onAnimationEnd(Animator animation) { mFolderName.animate().setDuration(FOLDER_NAME_ANIMATION_DURATION) @@ -607,7 +622,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList } @Override - public void onDragStart(DragSource source, Object info, int dragAction) { } + public void onDragStart(DragSource source, ItemInfo info, int dragAction) { } @Override public void onDragEnd() { @@ -630,12 +645,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList public void animateClosed() { if (!(getParent() instanceof DragLayer)) return; - PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0); - PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 0.9f); - PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 0.9f); - final ObjectAnimator oa = - LauncherAnimUtils.ofPropertyValuesHolder(this, alpha, scaleX, scaleY); - + final ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(this, 0, 0.9f, 0.9f); oa.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { @@ -656,7 +666,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList } public boolean acceptDrop(DragObject d) { - final ItemInfo item = (ItemInfo) d.dragInfo; + final ItemInfo item = d.dragInfo; final int itemType = item.itemType; return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION || itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) && @@ -896,7 +906,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList @Override public boolean supportsAppInfoDropTarget() { - return false; + return true; } @Override diff --git a/src/com/android/launcher3/FolderIcon.java b/src/com/android/launcher3/FolderIcon.java index 8d534d2fe..bd61a6de5 100644 --- a/src/com/android/launcher3/FolderIcon.java +++ b/src/com/android/launcher3/FolderIcon.java @@ -43,6 +43,8 @@ import android.widget.TextView; import com.android.launcher3.DropTarget.DragObject; import com.android.launcher3.FolderInfo.FolderListener; +import com.android.launcher3.dragndrop.DragLayer; +import com.android.launcher3.dragndrop.DragView; import com.android.launcher3.util.Thunk; import java.util.ArrayList; @@ -129,7 +131,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { private void init() { mLongPressHelper = new CheckLongPressHelper(this); - mStylusEventHelper = new StylusEventHelper(this); + mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this); setAccessibilityDelegate(LauncherAppState.getInstance().getAccessibilityDelegate()); } @@ -171,8 +173,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { icon.setOnClickListener(launcher); icon.mInfo = folderInfo; icon.mLauncher = launcher; - icon.setContentDescription(String.format(launcher.getString(R.string.folder_name_format), - folderInfo.title)); + icon.setContentDescription(launcher.getString(R.string.folder_name_format, folderInfo.title)); Folder folder = Folder.fromXml(launcher); folder.setDragController(launcher.getDragController()); folder.setFolderIcon(icon); @@ -330,8 +331,8 @@ public class FolderIcon extends FrameLayout implements FolderListener { !mFolder.isFull() && item != mInfo && !mInfo.opened); } - public boolean acceptDrop(Object dragInfo) { - final ItemInfo item = (ItemInfo) dragInfo; + public boolean acceptDrop(ItemInfo dragInfo) { + final ItemInfo item = dragInfo; return !mFolder.isDestroyed() && willAcceptItem(item); } @@ -339,8 +340,8 @@ public class FolderIcon extends FrameLayout implements FolderListener { mInfo.add(item); } - public void onDragEnter(Object dragInfo) { - if (mFolder.isDestroyed() || !willAcceptItem((ItemInfo) dragInfo)) return; + public void onDragEnter(ItemInfo dragInfo) { + if (mFolder.isDestroyed() || !willAcceptItem(dragInfo)) return; CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams(); CellLayout layout = (CellLayout) getParent().getParent(); mFolderRingAnimator.setCell(lp.cellX, lp.cellY); @@ -356,7 +357,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { // Workspace#onDropExternal. mOpenAlarm.setAlarm(ON_OPEN_DELAY); } - mDragInfo = (ItemInfo) dragInfo; + mDragInfo = dragInfo; } public void onDragOver(Object dragInfo) { @@ -712,8 +713,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { public void onTitleChanged(CharSequence title) { mFolderName.setText(title); - setContentDescription(String.format(getContext().getString(R.string.folder_name_format), - title)); + setContentDescription(getContext().getString(R.string.folder_name_format, title)); } @Override @@ -723,7 +723,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { boolean result = super.onTouchEvent(event); // Check for a stylus button press, if it occurs cancel any long press checks. - if (mStylusEventHelper.checkAndPerformStylusEvent(event)) { + if (mStylusEventHelper.onMotionEvent(event)) { mLongPressHelper.cancelLongPress(); return true; } diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java index aea21c95b..6e0dcd421 100644 --- a/src/com/android/launcher3/FolderInfo.java +++ b/src/com/android/launcher3/FolderInfo.java @@ -22,7 +22,6 @@ import android.content.Context; import com.android.launcher3.compat.UserHandleCompat; import java.util.ArrayList; -import java.util.Arrays; /** * Represents a folder containing shortcuts or apps. @@ -140,7 +139,7 @@ public class FolderInfo extends ItemInfo { return "FolderInfo(id=" + this.id + " type=" + this.itemType + " container=" + this.container + " screen=" + screenId + " cellX=" + cellX + " cellY=" + cellY + " spanX=" + spanX - + " spanY=" + spanY + " dropPos=" + Arrays.toString(dropPos) + ")"; + + " spanY=" + spanY + ")"; } public boolean hasOption(int optionFlag) { diff --git a/src/com/android/launcher3/FolderPagedView.java b/src/com/android/launcher3/FolderPagedView.java index cc9c5738a..7aff8322e 100644 --- a/src/com/android/launcher3/FolderPagedView.java +++ b/src/com/android/launcher3/FolderPagedView.java @@ -30,6 +30,7 @@ import android.view.animation.OvershootInterpolator; import com.android.launcher3.FocusHelper.PagedFolderKeyEventListener; import com.android.launcher3.PageIndicator.PageMarkerResources; import com.android.launcher3.Workspace.ItemOperator; +import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.util.Thunk; import java.util.ArrayList; @@ -435,8 +436,7 @@ public class FolderPagedView extends PagedView { } public String getAccessibilityDescription() { - return String.format(getContext().getString(R.string.folder_opened), - mGridCountX, mGridCountY); + return getContext().getString(R.string.folder_opened, mGridCountX, mGridCountY); } /** diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java index 17fdeb1dc..a9cff5e65 100644 --- a/src/com/android/launcher3/Hotseat.java +++ b/src/com/android/launcher3/Hotseat.java @@ -17,8 +17,6 @@ package com.android.launcher3; import android.content.Context; -import android.content.res.Configuration; -import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.util.AttributeSet; diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java index 59ab8397d..bf8ee9d61 100644 --- a/src/com/android/launcher3/IconCache.java +++ b/src/com/android/launcher3/IconCache.java @@ -106,7 +106,6 @@ public class IconCache { private final BitmapFactory.Options mLowResOptions; private String mSystemState; - private Bitmap mLowResBitmap; private Canvas mLowResCanvas; private Paint mLowResPaint; @@ -117,6 +116,8 @@ public class IconCache { mLauncherApps = LauncherAppsCompat.getInstance(mContext); mIconDpi = inv.fillResIconDpi; mIconDb = new IconDB(context); + mLowResCanvas = new Canvas(); + mLowResPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG); mWorkerHandler = new Handler(LauncherModel.getWorkerLooper()); @@ -239,7 +240,7 @@ public class IconCache { long userSerial = mUserManager.getSerialNumberForUser(user); mIconDb.getWritableDatabase().delete(IconDB.TABLE_NAME, IconDB.COLUMN_COMPONENT + " LIKE ? AND " + IconDB.COLUMN_USER + " = ?", - new String[] {packageName + "/%", Long.toString(userSerial)}); + new String[]{packageName + "/%", Long.toString(userSerial)}); } public void updateDbIcons(Set<String> ignorePackagesForMainUser) { @@ -386,7 +387,8 @@ public class IconCache { entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, app.getUser()); mCache.put(new ComponentKey(app.getComponentName(), app.getUser()), entry); - return newContentValues(entry.icon, entry.title.toString(), mActivityBgColor); + Bitmap lowResIcon = generateLowResIcon(entry.icon, mActivityBgColor); + return newContentValues(entry.icon, lowResIcon, entry.title.toString()); } /** @@ -623,17 +625,22 @@ public class IconCache { if (appInfo == null) { throw new NameNotFoundException("ApplicationInfo is null"); } + + // Load the full res icon for the application, but if useLowResIcon is set, then + // only keep the low resolution icon instead of the larger full-sized icon Drawable drawable = mUserManager.getBadgedDrawableForUser( appInfo.loadIcon(mPackageManager), user); - entry.icon = Utilities.createIconBitmap(drawable, mContext); + Bitmap icon = Utilities.createIconBitmap(drawable, mContext); + Bitmap lowResIcon = generateLowResIcon(icon, mPackageBgColor); entry.title = appInfo.loadLabel(mPackageManager); entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user); - entry.isLowResIcon = false; + entry.icon = useLowResIcon ? lowResIcon : icon; + entry.isLowResIcon = useLowResIcon; // Add the icon in the DB here, since these do not get written during // package updates. ContentValues values = - newContentValues(entry.icon, entry.title.toString(), mPackageBgColor); + newContentValues(icon, lowResIcon, entry.title.toString()); addIconToDB(values, cacheKey.componentName, info, mUserManager.getSerialNumberForUser(user)); @@ -673,9 +680,9 @@ public class IconCache { // pass } - ContentValues values = newContentValues( - Bitmap.createScaledBitmap(icon, idp.iconBitmapSize, idp.iconBitmapSize, true), - label, Color.TRANSPARENT); + icon = Bitmap.createScaledBitmap(icon, idp.iconBitmapSize, idp.iconBitmapSize, true); + Bitmap lowResIcon = generateLowResIcon(icon, Color.TRANSPARENT); + ContentValues values = newContentValues(icon, lowResIcon, label); values.put(IconDB.COLUMN_COMPONENT, componentName.flattenToString()); values.put(IconDB.COLUMN_USER, userSerial); mIconDb.getWritableDatabase().insertWithOnConflict(IconDB.TABLE_NAME, null, values, @@ -841,34 +848,37 @@ public class IconCache { } } - private ContentValues newContentValues(Bitmap icon, String label, int lowResBackgroundColor) { + private ContentValues newContentValues(Bitmap icon, Bitmap lowResIcon, String label) { ContentValues values = new ContentValues(); values.put(IconDB.COLUMN_ICON, Utilities.flattenBitmap(icon)); + values.put(IconDB.COLUMN_ICON_LOW_RES, Utilities.flattenBitmap(lowResIcon)); values.put(IconDB.COLUMN_LABEL, label); values.put(IconDB.COLUMN_SYSTEM_STATE, mSystemState); + return values; + } + + /** + * Generates a new low-res icon given a high-res icon. + */ + private Bitmap generateLowResIcon(Bitmap icon, int lowResBackgroundColor) { if (lowResBackgroundColor == Color.TRANSPARENT) { - values.put(IconDB.COLUMN_ICON_LOW_RES, Utilities.flattenBitmap( - Bitmap.createScaledBitmap(icon, - icon.getWidth() / LOW_RES_SCALE_FACTOR, - icon.getHeight() / LOW_RES_SCALE_FACTOR, true))); + return Bitmap.createScaledBitmap(icon, + icon.getWidth() / LOW_RES_SCALE_FACTOR, + icon.getHeight() / LOW_RES_SCALE_FACTOR, true); } else { + Bitmap lowResIcon = Bitmap.createBitmap(icon.getWidth() / LOW_RES_SCALE_FACTOR, + icon.getHeight() / LOW_RES_SCALE_FACTOR, Bitmap.Config.RGB_565); synchronized (this) { - if (mLowResBitmap == null) { - mLowResBitmap = Bitmap.createBitmap(icon.getWidth() / LOW_RES_SCALE_FACTOR, - icon.getHeight() / LOW_RES_SCALE_FACTOR, Bitmap.Config.RGB_565); - mLowResCanvas = new Canvas(mLowResBitmap); - mLowResPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG); - } mLowResCanvas.drawColor(lowResBackgroundColor); mLowResCanvas.drawBitmap(icon, new Rect(0, 0, icon.getWidth(), icon.getHeight()), - new Rect(0, 0, mLowResBitmap.getWidth(), mLowResBitmap.getHeight()), + new Rect(0, 0, lowResIcon.getWidth(), lowResIcon.getHeight()), mLowResPaint); - values.put(IconDB.COLUMN_ICON_LOW_RES, Utilities.flattenBitmap(mLowResBitmap)); + mLowResCanvas.setBitmap(null); } + return lowResIcon; } - return values; } private static Bitmap loadIconNoResize(Cursor c, int iconIndex, BitmapFactory.Options options) { diff --git a/src/com/android/launcher3/InfoDropTarget.java b/src/com/android/launcher3/InfoDropTarget.java index d93cdcc1b..d444640e6 100644 --- a/src/com/android/launcher3/InfoDropTarget.java +++ b/src/com/android/launcher3/InfoDropTarget.java @@ -20,9 +20,7 @@ import android.content.ComponentName; import android.content.Context; import android.util.AttributeSet; -import com.android.launcher3.compat.UserHandleCompat; - -public class InfoDropTarget extends ButtonDropTarget { +public class InfoDropTarget extends UninstallDropTarget { public InfoDropTarget(Context context, AttributeSet attrs) { this(context, attrs, 0); @@ -41,7 +39,10 @@ public class InfoDropTarget extends ButtonDropTarget { setDrawable(R.drawable.ic_info_launcher); } - public static void startDetailsActivityForInfo(Object info, Launcher launcher) { + /** + * @return Whether the activity was started. + */ + public static boolean startDetailsActivityForInfo(ItemInfo info, Launcher launcher) { ComponentName componentName = null; if (info instanceof AppInfo) { componentName = ((AppInfo) info).componentName; @@ -49,30 +50,28 @@ public class InfoDropTarget extends ButtonDropTarget { componentName = ((ShortcutInfo) info).intent.getComponent(); } else if (info instanceof PendingAddItemInfo) { componentName = ((PendingAddItemInfo) info).componentName; + } else if (info instanceof LauncherAppWidgetInfo) { + componentName = ((LauncherAppWidgetInfo) info).providerName; } - final UserHandleCompat user; - if (info instanceof ItemInfo) { - user = ((ItemInfo) info).user; - } else { - user = UserHandleCompat.myUserHandle(); - } - if (componentName != null) { - launcher.startApplicationDetailsActivity(componentName, user); + launcher.startApplicationDetailsActivity(componentName, info.user); + return true; } + return false; } @Override - protected boolean supportsDrop(DragSource source, Object info) { - return source.supportsAppInfoDropTarget() && supportsDrop(getContext(), info); + protected boolean startActivityWithUninstallAffordance(DragObject d) { + return startDetailsActivityForInfo(d.dragInfo, mLauncher); } - public static boolean supportsDrop(Context context, Object info) { - return info instanceof AppInfo || info instanceof PendingAddItemInfo; + @Override + protected boolean supportsDrop(DragSource source, ItemInfo info) { + return source.supportsAppInfoDropTarget() && supportsDrop(getContext(), info); } - @Override - void completeDrop(DragObject d) { - startDetailsActivityForInfo(d.dragInfo, mLauncher); + public static boolean supportsDrop(Context context, ItemInfo info) { + return info instanceof AppInfo || info instanceof ShortcutInfo + || info instanceof PendingAddItemInfo || info instanceof LauncherAppWidgetInfo; } } diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java index f7e0ea488..aa5a18d3c 100644 --- a/src/com/android/launcher3/ItemInfo.java +++ b/src/com/android/launcher3/ItemInfo.java @@ -24,8 +24,6 @@ import android.graphics.Bitmap; import com.android.launcher3.compat.UserHandleCompat; import com.android.launcher3.compat.UserManagerCompat; -import java.util.Arrays; - /** * Represents an item in the launcher. */ @@ -35,14 +33,14 @@ public class ItemInfo { * Intent extra to store the profile. Format: UserHandle */ static final String EXTRA_PROFILE = "profile"; - + public static final int NO_ID = -1; - + /** * The id in the settings database for this item */ public long id = NO_ID; - + /** * One of {@link LauncherSettings.Favorites#ITEM_TYPE_APPLICATION}, * {@link LauncherSettings.Favorites#ITEM_TYPE_SHORTCUT}, @@ -50,20 +48,20 @@ public class ItemInfo { * {@link LauncherSettings.Favorites#ITEM_TYPE_APPWIDGET}. */ public int itemType; - + /** - * The id of the container that holds this item. For the desktop, this will be + * The id of the container that holds this item. For the desktop, this will be * {@link LauncherSettings.Favorites#CONTAINER_DESKTOP}. For the all applications folder it * will be {@link #NO_ID} (since it is not stored in the settings DB). For user folders * it will be the id of the folder. */ public long container = NO_ID; - + /** * Iindicates the screen in which the shortcut appears. */ public long screenId = -1; - + /** * Indicates the X position of the associated cell. */ @@ -100,11 +98,6 @@ public class ItemInfo { public int rank = 0; /** - * Indicates that this item needs to be updated in the db - */ - public boolean requiresDbUpdate = false; - - /** * Title of the item */ public CharSequence title; @@ -114,11 +107,6 @@ public class ItemInfo { */ public CharSequence contentDescription; - /** - * The position of the item in a drag-and-drop operation. - */ - public int[] dropPos = null; - public UserHandleCompat user; public ItemInfo() { @@ -146,18 +134,11 @@ public class ItemInfo { } public Intent getIntent() { - throw new RuntimeException("Unexpected Intent"); + return null; } - /** - * Write the fields of this item to the DB - * - * @param context A context object to use for getting UserManagerCompat - * @param values - */ - - void onAddToDatabase(Context context, ContentValues values) { - values.put(LauncherSettings.BaseLauncherColumns.ITEM_TYPE, itemType); + public void writeToValues(ContentValues values) { + values.put(LauncherSettings.Favorites.ITEM_TYPE, itemType); values.put(LauncherSettings.Favorites.CONTAINER, container); values.put(LauncherSettings.Favorites.SCREEN, screenId); values.put(LauncherSettings.Favorites.CELLX, cellX); @@ -165,6 +146,27 @@ public class ItemInfo { values.put(LauncherSettings.Favorites.SPANX, spanX); values.put(LauncherSettings.Favorites.SPANY, spanY); values.put(LauncherSettings.Favorites.RANK, rank); + } + + public void readFromValues(ContentValues values) { + itemType = values.getAsInteger(LauncherSettings.Favorites.ITEM_TYPE); + container = values.getAsLong(LauncherSettings.Favorites.CONTAINER); + screenId = values.getAsLong(LauncherSettings.Favorites.SCREEN); + cellX = values.getAsInteger(LauncherSettings.Favorites.CELLX); + cellY = values.getAsInteger(LauncherSettings.Favorites.CELLY); + spanX = values.getAsInteger(LauncherSettings.Favorites.SPANX); + spanY = values.getAsInteger(LauncherSettings.Favorites.SPANY); + rank = values.getAsInteger(LauncherSettings.Favorites.RANK); + } + + /** + * Write the fields of this item to the DB + * + * @param context A context object to use for getting UserManagerCompat + * @param values + */ + void onAddToDatabase(Context context, ContentValues values) { + writeToValues(values); long serialNumber = UserManagerCompat.getInstance(context).getSerialNumberForUser(user); values.put(LauncherSettings.Favorites.PROFILE_ID, serialNumber); @@ -194,7 +196,6 @@ public class ItemInfo { public String toString() { return "Item(id=" + this.id + " type=" + this.itemType + " container=" + this.container + " screen=" + screenId + " cellX=" + cellX + " cellY=" + cellY + " spanX=" + spanX - + " spanY=" + spanY + " dropPos=" + Arrays.toString(dropPos) - + " user=" + user + ")"; + + " spanY=" + spanY + " user=" + user + ")"; } } diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 812445e85..8393910ef 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -20,7 +20,6 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; -import android.animation.PropertyValuesHolder; import android.animation.ValueAnimator; import android.annotation.SuppressLint; import android.annotation.TargetApi; @@ -36,6 +35,7 @@ import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ComponentCallbacks2; import android.content.ComponentName; +import android.content.ContentValues; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -71,10 +71,8 @@ import android.text.TextUtils; import android.text.method.TextKeyListener; import android.util.Log; import android.view.Display; -import android.view.Gravity; import android.view.HapticFeedbackConstants; import android.view.KeyEvent; -import android.view.LayoutInflater; import android.view.Menu; import android.view.MotionEvent; import android.view.Surface; @@ -90,7 +88,6 @@ import android.view.accessibility.AccessibilityEvent; import android.view.animation.OvershootInterpolator; import android.view.inputmethod.InputMethodManager; import android.widget.Advanceable; -import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; @@ -103,10 +100,16 @@ import com.android.launcher3.compat.LauncherActivityInfoCompat; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.UserHandleCompat; import com.android.launcher3.compat.UserManagerCompat; +import com.android.launcher3.config.ProviderConfig; +import com.android.launcher3.dragndrop.DragController; +import com.android.launcher3.dragndrop.DragLayer; +import com.android.launcher3.dragndrop.DragView; import com.android.launcher3.model.WidgetsModel; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.LongArrayMap; +import com.android.launcher3.util.TestingUtils; import com.android.launcher3.util.Thunk; +import com.android.launcher3.util.ViewOnDrawExecutor; import com.android.launcher3.widget.PendingAddWidgetInfo; import com.android.launcher3.widget.WidgetHostViewLoader; import com.android.launcher3.widget.WidgetsContainerView; @@ -123,7 +126,6 @@ import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; /** * Default launcher application. @@ -131,7 +133,7 @@ import java.util.concurrent.atomic.AtomicInteger; public class Launcher extends Activity implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks, View.OnTouchListener, PageSwitchListener, LauncherProviderChangeListener { - static final String TAG = "Launcher"; + public static final String TAG = "Launcher"; static final boolean LOGD = false; static final boolean PROFILE_STARTUP = false; @@ -162,8 +164,6 @@ public class Launcher extends Activity */ protected static final int REQUEST_LAST = 100; - static final int SCREEN_COUNT = 5; - // To turn on these properties, type // adb shell setprop log.tag.PROPERTY_NAME [VERBOSE | SUPPRESS] static final String DUMP_STATE_PROPERTY = "launcher_dump_state"; @@ -176,24 +176,12 @@ public class Launcher extends Activity private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen"; // Type: int private static final String RUNTIME_STATE = "launcher.state"; - // Type: int - private static final String RUNTIME_STATE_PENDING_ADD_CONTAINER = "launcher.add_container"; - // Type: int - private static final String RUNTIME_STATE_PENDING_ADD_SCREEN = "launcher.add_screen"; - // Type: int - private static final String RUNTIME_STATE_PENDING_ADD_CELL_X = "launcher.add_cell_x"; - // Type: int - private static final String RUNTIME_STATE_PENDING_ADD_CELL_Y = "launcher.add_cell_y"; - // Type: int - private static final String RUNTIME_STATE_PENDING_ADD_SPAN_X = "launcher.add_span_x"; - // Type: int - private static final String RUNTIME_STATE_PENDING_ADD_SPAN_Y = "launcher.add_span_y"; + // Type: Content Values / parcelable + private static final String RUNTIME_STATE_PENDING_ADD_ITEM = "launcher.add_item"; // Type: parcelable private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_INFO = "launcher.add_widget_info"; // Type: parcelable private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_ID = "launcher.add_widget_id"; - // Type: int[] - private static final String RUNTIME_STATE_VIEW_IDS = "launcher.view_ids"; static final String INTRO_SCREEN_DISMISSED = "launcher.intro_screen_dismissed"; static final String FIRST_RUN_ACTIVITY_DISPLAYED = "launcher.first_run_activity_displayed"; @@ -202,16 +190,14 @@ public class Launcher extends Activity static final String ACTION_FIRST_LOAD_COMPLETE = "com.android.launcher3.action.FIRST_LOAD_COMPLETE"; - public static final String SHOW_WEIGHT_WATCHER = "debug.show_mem"; - public static final boolean SHOW_WEIGHT_WATCHER_DEFAULT = false; - private static final String QSB_WIDGET_ID = "qsb_widget_id"; private static final String QSB_WIDGET_PROVIDER = "qsb_widget_provider"; public static final String USER_HAS_MIGRATED = "launcher.user_migrated_from_old_data"; /** The different states that Launcher can be in. */ - enum State { NONE, WORKSPACE, APPS, APPS_SPRING_LOADED, WIDGETS, WIDGETS_SPRING_LOADED } + enum State { NONE, WORKSPACE, WORKSPACE_SPRING_LOADED, APPS, APPS_SPRING_LOADED, + WIDGETS, WIDGETS_SPRING_LOADED } @Thunk State mState = State.WORKSPACE; @Thunk LauncherStateTransitionAnimation mStateTransitionAnimation; @@ -227,9 +213,6 @@ public class Launcher extends Activity private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500; private static final int ACTIVITY_START_DELAY = 1000; - private HashMap<Integer, Integer> mItemIdToViewId = new HashMap<Integer, Integer>(); - private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1); - // How long to wait before the new-shortcut animation automatically pans the workspace private static int NEW_APPS_PAGE_MOVE_DELAY = 500; private static int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5; @@ -238,19 +221,18 @@ public class Launcher extends Activity private final BroadcastReceiver mCloseSystemDialogsReceiver = new CloseSystemDialogsIntentReceiver(); - private LayoutInflater mInflater; - @Thunk Workspace mWorkspace; private View mLauncherView; private View mPageIndicators; @Thunk DragLayer mDragLayer; private DragController mDragController; - private View mWeightWatcher; + + public View mWeightWatcher; private AppWidgetManagerCompat mAppWidgetManager; private LauncherAppWidgetHost mAppWidgetHost; - @Thunk ItemInfo mPendingAddInfo = new ItemInfo(); + @Thunk final ItemInfo mPendingAddInfo = new ItemInfo(); private LauncherAppWidgetProviderInfo mPendingAddWidgetInfo; private int mPendingAddWidgetId = -1; @@ -263,6 +245,7 @@ public class Launcher extends Activity private View mWidgetsButton; private SearchDropTargetBar mSearchDropTargetBar; + private AppInfoDropTargetBar mAppInfoDropTargetBar; // Main container view for the all apps screen. @Thunk AllAppsContainerView mAppsView; @@ -271,7 +254,6 @@ public class Launcher extends Activity @Thunk WidgetsContainerView mWidgetsView; @Thunk WidgetsModel mWidgetsModel; - private boolean mAutoAdvanceRunning = false; private AppWidgetHostView mQsb; private Bundle mSavedState; @@ -291,8 +273,7 @@ public class Launcher extends Activity private ArrayList<Runnable> mBindOnResumeCallbacks = new ArrayList<Runnable>(); private ArrayList<Runnable> mOnResumeCallbacks = new ArrayList<Runnable>(); - - private Bundle mSavedInstanceState; + private ViewOnDrawExecutor mPendingExecutor; private LauncherModel mModel; private IconCache mIconCache; @@ -309,16 +290,17 @@ public class Launcher extends Activity // Related to the auto-advancing of widgets private final int ADVANCE_MSG = 1; - private final int mAdvanceInterval = 20000; - private final int mAdvanceStagger = 250; + private static final int ADVANCE_INTERVAL = 20000; + private static final int ADVANCE_STAGGER = 250; + + private boolean mAutoAdvanceRunning = false; private long mAutoAdvanceSentTime; private long mAutoAdvanceTimeLeft = -1; - @Thunk HashMap<View, AppWidgetProviderInfo> mWidgetsToAdvance = - new HashMap<View, AppWidgetProviderInfo>(); + @Thunk HashMap<View, AppWidgetProviderInfo> mWidgetsToAdvance = new HashMap<>(); // Determines how long to wait after a rotation before restoring the screen orientation to // match the sensor state. - private final int mRestoreScreenOrientationDelay = 500; + private static final int RESTORE_SCREEN_ORIENTATION_DELAY = 500; @Thunk Drawable mWorkspaceBackgroundDrawable; @@ -355,10 +337,9 @@ public class Launcher extends Activity protected static HashMap<String, CustomAppWidget> sCustomAppWidgets = new HashMap<String, CustomAppWidget>(); - private static final boolean ENABLE_CUSTOM_WIDGET_TEST = false; static { - if (ENABLE_CUSTOM_WIDGET_TEST) { - sCustomAppWidgets.put(DummyWidget.class.getName(), new DummyWidget()); + if (TestingUtils.ENABLE_CUSTOM_WIDGET_TEST) { + TestingUtils.addDummyWidget(sCustomAppWidgets); } } @@ -440,7 +421,6 @@ public class Launcher extends Activity mIconCache = app.getIconCache(); mDragController = new DragController(this); - mInflater = getLayoutInflater(); mStateTransitionAnimation = new LauncherStateTransitionAnimation(this); mStats = new Stats(this); @@ -497,7 +477,7 @@ public class Launcher extends Activity // In case we are on a device with locked rotation, we should look at preferences to check // if the user has specifically allowed rotation. if (!mRotationEnabled) { - mRotationEnabled = Utilities.isAllowRotationPrefEnabled(getApplicationContext(), false); + mRotationEnabled = Utilities.isAllowRotationPrefEnabled(getApplicationContext()); } // On large interfaces, or on devices that a user has specifically enabled screen rotation, @@ -645,44 +625,18 @@ public class Launcher extends Activity return mStats; } - public LayoutInflater getInflater() { - return mInflater; - } - public boolean isDraggingEnabled() { // We prevent dragging when we are loading the workspace as it is possible to pick up a view // that is subsequently removed from the workspace in startBinding(). return !isWorkspaceLoading(); } - @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) - public static int generateViewId() { - if (Utilities.ATLEAST_JB_MR1) { - return View.generateViewId(); - } else { - // View.generateViewId() is not available. The following fallback logic is a copy - // of its implementation. - for (;;) { - final int result = sNextGeneratedId.get(); - // aapt-generated IDs have the high byte nonzero; clamp to the range under that. - int newValue = result + 1; - if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0. - if (sNextGeneratedId.compareAndSet(result, newValue)) { - return result; - } - } - } - } - public int getViewIdForItem(ItemInfo info) { - // This cast is safe given the > 2B range for int. - int itemId = (int) info.id; - if (mItemIdToViewId.containsKey(itemId)) { - return mItemIdToViewId.get(itemId); - } - int viewId = generateViewId(); - mItemIdToViewId.put(itemId, viewId); - return viewId; + // aapt-generated IDs have the high byte nonzero; clamp to the range under that. + // This cast is safe as long as the id < 0x00FFFFFF + // Since we jail all the dynamically generated views, there should be no clashes + // with any other views. + return (int) info.id; } /** @@ -751,7 +705,7 @@ public class Launcher extends Activity } else if (requestCode == REQUEST_PICK_WALLPAPER) { if (resultCode == RESULT_OK && mWorkspace.isInOverviewMode()) { // User could have free-scrolled between pages before picking a wallpaper; make sure - // we move to the closest one now to avoid visual jump. + // we move to the closest one now. mWorkspace.setCurrentPage(mWorkspace.getPageNearestToCenterOfScreen()); showWorkspace(false); } @@ -837,24 +791,22 @@ public class Launcher extends Activity return; } - // The pattern used here is that a user PICKs a specific application, - // which, depending on the target, might need to CREATE the actual target. - - // For example, the user would PICK_SHORTCUT for "Music playlist", and we - // launch over to the Music app to actually CREATE_SHORTCUT. - if (resultCode == RESULT_OK && mPendingAddInfo.container != ItemInfo.NO_ID) { - final PendingAddArguments args = preparePendingAddArgs(requestCode, data, -1, - mPendingAddInfo); - if (isWorkspaceLocked()) { - sPendingAddItem = args; - } else { - completeAdd(args); + if (requestCode == REQUEST_CREATE_SHORTCUT) { + // Handle custom shortcuts created using ACTION_CREATE_SHORTCUT. + if (resultCode == RESULT_OK && mPendingAddInfo.container != ItemInfo.NO_ID) { + final PendingAddArguments args = preparePendingAddArgs(requestCode, data, -1, + mPendingAddInfo); + if (isWorkspaceLocked()) { + sPendingAddItem = args; + } else { + completeAdd(args); + mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded, + ON_ACTIVITY_RESULT_ANIMATION_DELAY, false); + } + } else if (resultCode == RESULT_CANCELED) { mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded, ON_ACTIVITY_RESULT_ANIMATION_DELAY, false); } - } else if (resultCode == RESULT_CANCELED) { - mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded, - ON_ACTIVITY_RESULT_ANIMATION_DELAY, false); } mDragLayer.clearAnimatedView(); @@ -1001,12 +953,6 @@ public class Launcher extends Activity mPaused = false; if (mRestoring || mOnResumeNeedsLoad) { setWorkspaceLoading(true); - - // If we're starting binding all over again, clear any bind calls we'd postponed in - // the past (see waitUntilResume) -- we don't need them since we're starting binding - // from scratch again - mBindOnResumeCallbacks.clear(); - mModel.startLoader(PagedView.INVALID_RESTORE_PAGE); mRestoring = false; mOnResumeNeedsLoad = false; @@ -1313,7 +1259,6 @@ public class Launcher extends Activity * * @param savedState The previous state. */ - @SuppressWarnings("unchecked") private void restoreState(Bundle savedState) { if (savedState == null) { return; @@ -1330,16 +1275,9 @@ public class Launcher extends Activity mWorkspace.setRestorePage(currentScreen); } - final long pendingAddContainer = savedState.getLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, -1); - final long pendingAddScreen = savedState.getLong(RUNTIME_STATE_PENDING_ADD_SCREEN, -1); - - if (pendingAddContainer != ItemInfo.NO_ID && pendingAddScreen > -1) { - mPendingAddInfo.container = pendingAddContainer; - mPendingAddInfo.screenId = pendingAddScreen; - mPendingAddInfo.cellX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_X); - mPendingAddInfo.cellY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_Y); - mPendingAddInfo.spanX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_X); - mPendingAddInfo.spanY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y); + ContentValues itemValues = savedState.getParcelable(RUNTIME_STATE_PENDING_ADD_ITEM); + if (itemValues != null) { + mPendingAddInfo.readFromValues(itemValues); AppWidgetProviderInfo info = savedState.getParcelable( RUNTIME_STATE_PENDING_ADD_WIDGET_INFO); mPendingAddWidgetInfo = info == null ? @@ -1349,9 +1287,6 @@ public class Launcher extends Activity setWaitingForResult(true); mRestoring = true; } - - mItemIdToViewId = (HashMap<Integer, Integer>) - savedState.getSerializable(RUNTIME_STATE_VIEW_IDS); } /** @@ -1436,9 +1371,12 @@ public class Launcher extends Activity mWorkspace.setup(dragController); dragController.addDragListener(mWorkspace); - // Get the search/delete bar + // Get the search/delete/uninstall bar mSearchDropTargetBar = (SearchDropTargetBar) mDragLayer.findViewById(R.id.search_drop_target_bar); + // Get the app info bar + mAppInfoDropTargetBar = (AppInfoDropTargetBar) + mDragLayer.findViewById(R.id.app_info_drop_target_bar); // Setup Apps and Widgets mAppsView = (AllAppsContainerView) findViewById(R.id.apps_view); @@ -1451,27 +1389,18 @@ public class Launcher extends Activity // Setup the drag controller (drop targets have to be added in reverse order in priority) dragController.setDragScoller(mWorkspace); - dragController.setScrollView(mDragLayer); dragController.setMoveTarget(mWorkspace); dragController.addDropTarget(mWorkspace); if (mSearchDropTargetBar != null) { mSearchDropTargetBar.setup(this, dragController); mSearchDropTargetBar.setQsbSearchBar(getOrCreateQsbBar()); } + if (mAppInfoDropTargetBar != null) { + mAppInfoDropTargetBar.setup(this, dragController); + } - if (getResources().getBoolean(R.bool.debug_memory_enabled)) { - Log.v(TAG, "adding WeightWatcher"); - mWeightWatcher = new WeightWatcher(this); - mWeightWatcher.setAlpha(0.5f); - ((FrameLayout) mLauncherView).addView(mWeightWatcher, - new FrameLayout.LayoutParams( - FrameLayout.LayoutParams.MATCH_PARENT, - FrameLayout.LayoutParams.WRAP_CONTENT, - Gravity.BOTTOM) - ); - - boolean show = shouldShowWeightWatcher(); - mWeightWatcher.setVisibility(show ? View.VISIBLE : View.GONE); + if (TestingUtils.MEMORY_DUMP_ENABLED) { + TestingUtils.addWeightWatcher(this); } } @@ -1508,7 +1437,7 @@ public class Launcher extends Activity * @return A View inflated from layoutResId. */ public View createShortcut(ViewGroup parent, ShortcutInfo info) { - BubbleTextView favorite = (BubbleTextView) mInflater.inflate(R.layout.app_icon, + BubbleTextView favorite = (BubbleTextView) getLayoutInflater().inflate(R.layout.app_icon, parent, false); favorite.applyFromShortcutInfo(info, mIconCache); favorite.setCompoundDrawablePadding(mDeviceProfile.iconDrawablePaddingPx); @@ -1525,7 +1454,6 @@ public class Launcher extends Activity private void completeAddShortcut(Intent data, long container, long screenId, int cellX, int cellY) { int[] cellXY = mTmpAddItemCellCoordinates; - int[] touchXY = mPendingAddInfo.dropPos; CellLayout layout = getCellLayout(container, screenId); ShortcutInfo info = InstallShortcutReceiver.fromShortcutIntent(this, data); @@ -1552,10 +1480,6 @@ public class Launcher extends Activity true)) { return; } - } else if (touchXY != null) { - // when dragging and dropping, just find the closest free spot - int[] result = layout.findNearestVacantArea(touchXY[0], touchXY[1], 1, 1, cellXY); - foundCellSpan = (result != null); } else { foundCellSpan = layout.findCellForSpan(cellXY, 1, 1); } @@ -1762,11 +1686,11 @@ public class Launcher extends Activity if (autoAdvanceRunning != mAutoAdvanceRunning) { mAutoAdvanceRunning = autoAdvanceRunning; if (autoAdvanceRunning) { - long delay = mAutoAdvanceTimeLeft == -1 ? mAdvanceInterval : mAutoAdvanceTimeLeft; + long delay = mAutoAdvanceTimeLeft == -1 ? ADVANCE_INTERVAL : mAutoAdvanceTimeLeft; sendAdvanceMessage(delay); } else { if (!mWidgetsToAdvance.isEmpty()) { - mAutoAdvanceTimeLeft = Math.max(0, mAdvanceInterval - + mAutoAdvanceTimeLeft = Math.max(0, ADVANCE_INTERVAL - (System.currentTimeMillis() - mAutoAdvanceSentTime)); } mHandler.removeMessages(ADVANCE_MSG); @@ -1783,7 +1707,7 @@ public class Launcher extends Activity int i = 0; for (View key: mWidgetsToAdvance.keySet()) { final View v = key.findViewById(mWidgetsToAdvance.get(key).autoAdvanceViewId); - final int delay = mAdvanceStagger * i; + final int delay = ADVANCE_STAGGER * i; if (v instanceof Advanceable) { mHandler.postDelayed(new Runnable() { public void run() { @@ -1793,7 +1717,7 @@ public class Launcher extends Activity } i++; } - sendAdvanceMessage(mAdvanceInterval); + sendAdvanceMessage(ADVANCE_INTERVAL); } return true; } @@ -1849,6 +1773,10 @@ public class Launcher extends Activity return mSearchDropTargetBar; } + public AppInfoDropTargetBar getAppInfoDropTargetBar() { + return mAppInfoDropTargetBar; + } + public LauncherAppWidgetHost getAppWidgetHost() { return mAppWidgetHost; } @@ -1983,19 +1911,15 @@ public class Launcher extends Activity if (mPendingAddInfo.container != ItemInfo.NO_ID && mPendingAddInfo.screenId > -1 && mWaitingForResult) { - outState.putLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, mPendingAddInfo.container); - outState.putLong(RUNTIME_STATE_PENDING_ADD_SCREEN, mPendingAddInfo.screenId); - outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_X, mPendingAddInfo.cellX); - outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_Y, mPendingAddInfo.cellY); - outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_X, mPendingAddInfo.spanX); - outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y, mPendingAddInfo.spanY); + ContentValues itemValues = new ContentValues(); + mPendingAddInfo.writeToValues(itemValues); + outState.putParcelable(RUNTIME_STATE_PENDING_ADD_ITEM, itemValues); outState.putParcelable(RUNTIME_STATE_PENDING_ADD_WIDGET_INFO, mPendingAddWidgetInfo); outState.putInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID, mPendingAddWidgetId); } // Save the current widgets tray? // TODO(hyunyoungs) - outState.putSerializable(RUNTIME_STATE_VIEW_IDS, mItemIdToViewId); if (mLauncherCallbacks != null) { mLauncherCallbacks.onSaveInstanceState(outState); @@ -2243,7 +2167,6 @@ public class Launcher extends Activity mPendingAddInfo.cellX = mPendingAddInfo.cellY = -1; mPendingAddInfo.spanX = mPendingAddInfo.spanY = -1; mPendingAddInfo.minSpanX = mPendingAddInfo.minSpanY = 1; - mPendingAddInfo.dropPos = null; } void addAppWidgetImpl(final int appWidgetId, final ItemInfo info, final @@ -2315,7 +2238,6 @@ public class Launcher extends Activity resetAddInfo(); mPendingAddInfo.container = container; mPendingAddInfo.screenId = screenId; - mPendingAddInfo.dropPos = null; if (cell != null) { mPendingAddInfo.cellX = cell[0]; @@ -2339,7 +2261,6 @@ public class Launcher extends Activity resetAddInfo(); mPendingAddInfo.container = info.container = container; mPendingAddInfo.screenId = info.screenId = screenId; - mPendingAddInfo.dropPos = null; mPendingAddInfo.minSpanX = info.minSpanX; mPendingAddInfo.minSpanY = info.minSpanY; @@ -2561,8 +2482,10 @@ public class Launcher extends Activity if (v instanceof CellLayout) { if (mWorkspace.isInOverviewMode()) { - showWorkspace(mWorkspace.indexOfChild(v), true); + mWorkspace.snapToPageFromOverView(mWorkspace.indexOfChild(v)); + showWorkspace(true); } + return; } Object tag = v.getTag(); @@ -2693,21 +2616,6 @@ public class Launcher extends Activity return; } - final Intent intent = shortcut.intent; - - // Check for special shortcuts - if (intent.getComponent() != null) { - final String shortcutClass = intent.getComponent().getClassName(); - - if (shortcutClass.equals(MemoryDumpActivity.class.getName())) { - MemoryDumpActivity.startDump(this); - return; - } else if (shortcutClass.equals(ToggleWeightWatcher.class.getName())) { - toggleShowWeightWatcher(); - return; - } - } - // Check for abandoned promise if ((v instanceof BubbleTextView) && shortcut.isPromise() @@ -3093,10 +3001,6 @@ public class Launcher extends Activity private void growAndFadeOutFolderIcon(FolderIcon fi) { if (fi == null) return; - PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0); - PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.5f); - PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.5f); - FolderInfo info = (FolderInfo) fi.getTag(); if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { CellLayout cl = (CellLayout) fi.getParent().getParent(); @@ -3108,8 +3012,8 @@ public class Launcher extends Activity copyFolderIconToImage(fi); fi.setVisibility(View.INVISIBLE); - ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(mFolderIconImageView, alpha, - scaleX, scaleY); + ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale( + mFolderIconImageView, 0, 1.5f, 1.5f); if (Utilities.ATLEAST_LOLLIPOP) { oa.setInterpolator(new LogDecelerateInterpolator(100, 0)); } @@ -3119,17 +3023,12 @@ public class Launcher extends Activity private void shrinkAndFadeInFolderIcon(final FolderIcon fi) { if (fi == null) return; - PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1.0f); - PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f); - PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f); - final CellLayout cl = (CellLayout) fi.getParent().getParent(); // We remove and re-draw the FolderIcon in-case it has changed mDragLayer.removeView(mFolderIconImageView); copyFolderIconToImage(fi); - ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(mFolderIconImageView, alpha, - scaleX, scaleY); + ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(mFolderIconImageView, 1, 1, 1); oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration)); oa.addListener(new AnimatorListenerAdapter() { @Override @@ -3348,38 +3247,17 @@ public class Launcher extends Activity } } - /** - * @return whether or not the Launcher state changed. - */ public boolean showWorkspace(boolean animated) { - return showWorkspace(WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, animated, null); + return showWorkspace(animated, null); } - /** - * @return whether or not the Launcher state changed. - */ public boolean showWorkspace(boolean animated, Runnable onCompleteRunnable) { - return showWorkspace(WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, animated, - onCompleteRunnable); - } - - /** - * @return whether or not the Launcher state changed. - */ - protected boolean showWorkspace(int snapToPage, boolean animated) { - return showWorkspace(snapToPage, animated, null); - } - - /** - * @return whether or not the Launcher state changed. - */ - boolean showWorkspace(int snapToPage, boolean animated, Runnable onCompleteRunnable) { boolean changed = mState != State.WORKSPACE || mWorkspace.getState() != Workspace.State.NORMAL; if (changed) { mWorkspace.setVisibility(View.VISIBLE); mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(), - Workspace.State.NORMAL, snapToPage, animated, onCompleteRunnable); + Workspace.State.NORMAL, animated, onCompleteRunnable); // Set focus to the AppsCustomize button if (mAllAppsButton != null) { @@ -3405,9 +3283,7 @@ public class Launcher extends Activity void showOverviewMode(boolean animated) { mWorkspace.setVisibility(View.VISIBLE); mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(), - Workspace.State.OVERVIEW, - WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, animated, - null /* onCompleteRunnable */); + Workspace.State.OVERVIEW, animated, null /* onCompleteRunnable */); mState = State.WORKSPACE; } @@ -3484,31 +3360,48 @@ public class Launcher extends Activity * Updates the workspace and interaction state on state change, and return the animation to this * new state. */ - public Animator startWorkspaceStateChangeAnimation(Workspace.State toState, int toPage, + public Animator startWorkspaceStateChangeAnimation(Workspace.State toState, boolean animated, HashMap<View, Integer> layerViews) { Workspace.State fromState = mWorkspace.getState(); - Animator anim = mWorkspace.setStateWithAnimation(toState, toPage, animated, layerViews); + Animator anim = mWorkspace.setStateWithAnimation(toState, animated, layerViews); updateInteraction(fromState, toState); return anim; } + public void onLauncherClingShown() { + // When a launcher cling appears, it should cover the underlying layers, so their focus + // should be blocked. + if (mDragLayer.getDescendantFocusability() != ViewGroup.FOCUS_BLOCK_DESCENDANTS) { + mDragLayer.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); + } + } + + public void onLauncherClingDismissed() { + mDragLayer.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); + } + public void enterSpringLoadedDragMode() { if (LOGD) Log.d(TAG, String.format("enterSpringLoadedDragMode [mState=%s", mState.name())); - if (mState == State.WORKSPACE || mState == State.APPS_SPRING_LOADED || - mState == State.WIDGETS_SPRING_LOADED) { + if (isStateSpringLoaded()) { return; } mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(), - Workspace.State.SPRING_LOADED, - WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, true /* animated */, + Workspace.State.SPRING_LOADED, true /* animated */, null /* onCompleteRunnable */); - mState = isAppsViewVisible() ? State.APPS_SPRING_LOADED : State.WIDGETS_SPRING_LOADED; + + if (isAppsViewVisible()) { + mState = State.APPS_SPRING_LOADED; + } else if (isWidgetsViewVisible()) { + mState = State.WIDGETS_SPRING_LOADED; + } else { + mState = State.WORKSPACE_SPRING_LOADED; + } } public void exitSpringLoadedDragModeDelayed(final boolean successfulDrop, int delay, final Runnable onCompleteRunnable) { - if (mState != State.APPS_SPRING_LOADED && mState != State.WIDGETS_SPRING_LOADED) return; + if (!isStateSpringLoaded()) return; mHandler.postDelayed(new Runnable() { @Override @@ -3528,12 +3421,19 @@ public class Launcher extends Activity }, delay); } + private boolean isStateSpringLoaded() { + return mState == State.WORKSPACE_SPRING_LOADED || mState == State.APPS_SPRING_LOADED + || mState == State.WIDGETS_SPRING_LOADED; + } + void exitSpringLoadedDragMode() { if (mState == State.APPS_SPRING_LOADED) { showAppsView(true /* animated */, false /* resetListToTop */, false /* updatePredictedApps */, false /* focusSearchBar */); } else if (mState == State.WIDGETS_SPRING_LOADED) { showWidgetsView(true, false); + } else if (mState == State.WORKSPACE_SPRING_LOADED) { + showWorkspace(true); } } @@ -3722,7 +3622,20 @@ public class Launcher extends Activity if (mWorkspace != null) { return mWorkspace.getCurrentPage(); } else { - return SCREEN_COUNT / 2; + return 0; + } + } + + /** + * Clear any pending bind callbacks. This is called when is loader is planning to + * perform a full rebind from scratch. + */ + @Override + public void clearPendingBinds() { + mBindOnResumeCallbacks.clear(); + if (mPendingExecutor != null) { + mPendingExecutor.markCompleted(); + mPendingExecutor = null; } } @@ -3734,11 +3647,6 @@ public class Launcher extends Activity public void startBinding() { setWorkspaceLoading(true); - // If we're starting binding all over again, clear any bind calls we'd postponed in - // the past (see waitUntilResume) -- we don't need them since we're starting binding - // from scratch again - mBindOnResumeCallbacks.clear(); - // Clear the workspace because it's going to be rebound mWorkspace.clearDropTargets(); mWorkspace.removeAllWorkspaceScreens(); @@ -3774,30 +3682,6 @@ public class Launcher extends Activity } } - private boolean shouldShowWeightWatcher() { - String spKey = LauncherAppState.getSharedPreferencesKey(); - SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_PRIVATE); - boolean show = sp.getBoolean(SHOW_WEIGHT_WATCHER, SHOW_WEIGHT_WATCHER_DEFAULT); - - return show; - } - - private void toggleShowWeightWatcher() { - String spKey = LauncherAppState.getSharedPreferencesKey(); - SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_PRIVATE); - boolean show = sp.getBoolean(SHOW_WEIGHT_WATCHER, true); - - show = !show; - - SharedPreferences.Editor editor = sp.edit(); - editor.putBoolean(SHOW_WEIGHT_WATCHER, show); - editor.commit(); - - if (mWeightWatcher != null) { - mWeightWatcher.setVisibility(show ? View.VISIBLE : View.GONE); - } - } - public void bindAppsAdded(final ArrayList<Long> newScreens, final ArrayList<ItemInfo> addNotAnimated, final ArrayList<ItemInfo> addAnimated, @@ -3883,7 +3767,7 @@ public class Launcher extends Activity Object tag = v.getTag(); String desc = "Collision while binding workspace item: " + item + ". Collides with " + tag; - if (LauncherAppState.isDogfoodBuild()) { + if (ProviderConfig.IS_DOGFOOD_BUILD) { throw (new RuntimeException(desc)); } else { Log.d(TAG, desc); @@ -4103,6 +3987,21 @@ public class Launcher extends Activity mSynchronouslyBoundPages.add(page); } + @Override + public void executeOnNextDraw(ViewOnDrawExecutor executor) { + if (mPendingExecutor != null) { + mPendingExecutor.markCompleted(); + } + mPendingExecutor = executor; + executor.attachTo(this); + } + + public void clearPendingExecutor(ViewOnDrawExecutor executor) { + if (mPendingExecutor == executor) { + mPendingExecutor = null; + } + } + /** * Callback saying that there aren't any more items to bind. * @@ -4179,10 +4078,7 @@ public class Launcher extends Activity } private ValueAnimator createNewAppBounceAnimation(View v, int i) { - ValueAnimator bounceAnim = LauncherAnimUtils.ofPropertyValuesHolder(v, - PropertyValuesHolder.ofFloat("alpha", 1f), - PropertyValuesHolder.ofFloat("scaleX", 1f), - PropertyValuesHolder.ofFloat("scaleY", 1f)); + ValueAnimator bounceAnim = LauncherAnimUtils.ofViewAlphaAndScale(v, 1, 1, 1); bounceAnim.setDuration(InstallShortcutReceiver.NEW_SHORTCUT_BOUNCE_DURATION); bounceAnim.setStartDelay(i * InstallShortcutReceiver.NEW_SHORTCUT_STAGGER_DELAY); bounceAnim.setInterpolator(new OvershootInterpolator(BOUNCE_ANIMATION_TENSION)); @@ -4441,7 +4337,7 @@ public class Launcher extends Activity public void run() { setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); } - }, mRestoreScreenOrientationDelay); + }, RESTORE_SCREEN_ORIENTATION_DELAY); } } } @@ -4708,7 +4604,6 @@ public class Launcher extends Activity Log.d(TAG, "mWorkspaceLoading=" + mWorkspaceLoading); Log.d(TAG, "mRestoring=" + mRestoring); Log.d(TAG, "mWaitingForResult=" + mWaitingForResult); - Log.d(TAG, "mSavedInstanceState=" + mSavedInstanceState); Log.d(TAG, "sFolders.size=" + sFolders.size()); mModel.dumpState(); // TODO(hyunyoungs): add mWidgetsView.dumpState(); or mWidgetsModel.dumpState(); diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java index 6a248a332..853c2ec6b 100644 --- a/src/com/android/launcher3/LauncherAnimUtils.java +++ b/src/com/android/launcher3/LauncherAnimUtils.java @@ -21,14 +21,10 @@ import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; import android.animation.ValueAnimator; -import android.annotation.TargetApi; -import android.os.Build; +import android.util.Property; import android.view.View; -import android.view.ViewAnimationUtils; import android.view.ViewTreeObserver; -import com.android.launcher3.util.UiThreadCircularReveal; - import java.util.HashSet; import java.util.WeakHashMap; @@ -102,42 +98,32 @@ public class LauncherAnimUtils { return anim; } - public static ObjectAnimator ofFloat(View target, String propertyName, float... values) { - ObjectAnimator anim = new ObjectAnimator(); - anim.setTarget(target); - anim.setPropertyName(propertyName); - anim.setFloatValues(values); + public static ObjectAnimator ofFloat(View target, Property<View, Float> property, + float... values) { + ObjectAnimator anim = ObjectAnimator.ofFloat(target, property, values); cancelOnDestroyActivity(anim); new FirstFrameAnimatorHelper(anim, target); return anim; } + public static ObjectAnimator ofViewAlphaAndScale(View target, + float alpha, float scaleX, float scaleY) { + return ofPropertyValuesHolder(target, + PropertyValuesHolder.ofFloat(View.ALPHA, alpha), + PropertyValuesHolder.ofFloat(View.SCALE_X, scaleX), + PropertyValuesHolder.ofFloat(View.SCALE_Y, scaleY)); + } + public static ObjectAnimator ofPropertyValuesHolder(View target, PropertyValuesHolder... values) { - ObjectAnimator anim = new ObjectAnimator(); - anim.setTarget(target); - anim.setValues(values); - cancelOnDestroyActivity(anim); - new FirstFrameAnimatorHelper(anim, target); - return anim; + return ofPropertyValuesHolder(target, target, values); } public static ObjectAnimator ofPropertyValuesHolder(Object target, View view, PropertyValuesHolder... values) { - ObjectAnimator anim = new ObjectAnimator(); - anim.setTarget(target); - anim.setValues(values); + ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(target, values); cancelOnDestroyActivity(anim); new FirstFrameAnimatorHelper(anim, view); return anim; } - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - public static ValueAnimator createCircularReveal(View view, int centerX, - int centerY, float startRadius, float endRadius) { - ValueAnimator anim = UiThreadCircularReveal.createCircularReveal(view, centerX, - centerY, startRadius, endRadius); - new FirstFrameAnimatorHelper(anim, view); - return anim; - } } diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java index d87ad67e5..3b207a9cf 100644 --- a/src/com/android/launcher3/LauncherAppState.java +++ b/src/com/android/launcher3/LauncherAppState.java @@ -17,16 +17,17 @@ package com.android.launcher3; import android.app.SearchManager; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.os.UserManager; import android.util.Log; import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.PackageInstallerCompat; import com.android.launcher3.compat.UserManagerCompat; +import com.android.launcher3.util.TestingUtils; import com.android.launcher3.util.Thunk; import java.lang.ref.WeakReference; @@ -34,12 +35,11 @@ import java.lang.ref.WeakReference; public class LauncherAppState { private final AppFilter mAppFilter; - private final BuildInfo mBuildInfo; @Thunk final LauncherModel mModel; private final IconCache mIconCache; private final WidgetPreviewLoader mWidgetCache; - private boolean mWallpaperChangedSinceLastCheck; + @Thunk boolean mWallpaperChangedSinceLastCheck; private static WeakReference<LauncherProvider> sLauncherProvider; private static Context sContext; @@ -79,8 +79,8 @@ public class LauncherAppState { Log.v(Launcher.TAG, "LauncherAppState inited"); - if (sContext.getResources().getBoolean(R.bool.debug_memory_enabled)) { - MemoryTracker.startTrackingMe(sContext, "L"); + if (TestingUtils.MEMORY_DUMP_ENABLED) { + TestingUtils.startTrackingMemory(sContext); } mInvariantDeviceProfile = new InvariantDeviceProfile(sContext); @@ -88,7 +88,6 @@ public class LauncherAppState { mWidgetCache = new WidgetPreviewLoader(sContext, mIconCache); mAppFilter = AppFilter.loadByName(sContext.getString(R.string.app_filter_class)); - mBuildInfo = BuildInfo.loadByName(sContext.getString(R.string.build_info_class)); mModel = new LauncherModel(this, mIconCache, mAppFilter); LauncherAppsCompat.getInstance(sContext).addOnAppsChangedCallback(mModel); @@ -103,6 +102,16 @@ public class LauncherAppState { sContext.registerReceiver(mModel, filter); UserManagerCompat.getInstance(sContext).enableAndResetCache(); + + if (!Utilities.ATLEAST_KITKAT) { + sContext.registerReceiver(new BroadcastReceiver() { + + @Override + public void onReceive(Context context, Intent intent) { + mWallpaperChangedSinceLastCheck = true; + } + }, new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED)); + } } /** @@ -159,10 +168,6 @@ public class LauncherAppState { public WidgetPreviewLoader getWidgetCache() { return mWidgetCache; } - - public void onWallpaperChanged() { - mWallpaperChangedSinceLastCheck = true; - } public boolean hasWallpaperChangedSinceLastCheck() { boolean result = mWallpaperChangedSinceLastCheck; @@ -173,8 +178,4 @@ public class LauncherAppState { public InvariantDeviceProfile getInvariantDeviceProfile() { return mInvariantDeviceProfile; } - - public static boolean isDogfoodBuild() { - return getInstance().mBuildInfo.isDogfoodBuild(); - } } diff --git a/src/com/android/launcher3/LauncherAppWidgetHostView.java b/src/com/android/launcher3/LauncherAppWidgetHostView.java index cf461a5b8..01332fb9e 100644 --- a/src/com/android/launcher3/LauncherAppWidgetHostView.java +++ b/src/com/android/launcher3/LauncherAppWidgetHostView.java @@ -26,7 +26,8 @@ import android.view.ViewConfiguration; import android.view.ViewGroup; import android.widget.RemoteViews; -import com.android.launcher3.DragLayer.TouchCompleteListener; +import com.android.launcher3.dragndrop.DragLayer; +import com.android.launcher3.dragndrop.DragLayer.TouchCompleteListener; /** * {@inheritDoc} @@ -47,7 +48,7 @@ public class LauncherAppWidgetHostView extends AppWidgetHostView implements Touc super(context); mContext = context; mLongPressHelper = new CheckLongPressHelper(this); - mStylusEventHelper = new StylusEventHelper(this); + mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this); mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); mDragLayer = ((Launcher) context).getDragLayer(); setAccessibilityDelegate(LauncherAppState.getInstance().getAccessibilityDelegate()); @@ -93,7 +94,7 @@ public class LauncherAppWidgetHostView extends AppWidgetHostView implements Touc // Watch for longpress or stylus button press events at this level to // make sure users can always pick up this widget - if (mStylusEventHelper.checkAndPerformStylusEvent(ev)) { + if (mStylusEventHelper.onMotionEvent(ev)) { mLongPressHelper.cancelLongPress(); return true; } diff --git a/src/com/android/launcher3/LauncherClings.java b/src/com/android/launcher3/LauncherClings.java index 18fe8ef86..43d05a659 100644 --- a/src/com/android/launcher3/LauncherClings.java +++ b/src/com/android/launcher3/LauncherClings.java @@ -27,9 +27,11 @@ import android.os.Build; import android.os.Bundle; import android.os.UserManager; import android.provider.Settings; +import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; +import android.view.View.OnKeyListener; import android.view.View.OnLongClickListener; import android.view.ViewGroup; import android.view.ViewTreeObserver.OnGlobalLayoutListener; @@ -37,7 +39,7 @@ import android.view.accessibility.AccessibilityManager; import com.android.launcher3.util.Thunk; -class LauncherClings implements OnClickListener { +class LauncherClings implements OnClickListener, OnKeyListener { private static final String MIGRATION_CLING_DISMISSED_KEY = "cling_gel.migration.dismissed"; private static final String WORKSPACE_CLING_DISMISSED_KEY = "cling_gel.workspace.dismissed"; @@ -85,6 +87,20 @@ class LauncherClings implements OnClickListener { } } + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (event.isPrintingKey()) { + // Should ignore all printing keys, otherwise they come to the search box. + return true; + } + if (keyCode == KeyEvent.KEYCODE_MENU) { + // Menu key goes to the overview mode similar to longpress, therefore it needs to + // dismiss the clings. + dismissLongPressCling(); + } + return false; + } + /** * Shows the migration cling. * @@ -92,6 +108,7 @@ class LauncherClings implements OnClickListener { * package was not preinstalled and there exists a db to migrate from. */ public void showMigrationCling() { + mLauncher.onLauncherClingShown(); mIsVisible = true; mLauncher.hideWorkspaceSearchAndHotseat(); @@ -136,7 +153,9 @@ class LauncherClings implements OnClickListener { final ViewGroup content = (ViewGroup) cling.findViewById(R.id.cling_content); mInflater.inflate(showWelcome ? R.layout.longpress_cling_welcome_content : R.layout.longpress_cling_content, content); - content.findViewById(R.id.cling_dismiss_longpress_info).setOnClickListener(this); + final View button = content.findViewById(R.id.cling_dismiss_longpress_info); + button.setOnClickListener(this); + button.setOnKeyListener(this); if (TAG_CROP_TOP_AND_SIDES.equals(content.getTag())) { Drawable bg = new BorderCropDrawable(mLauncher.getResources().getDrawable(R.drawable.cling_bg), @@ -144,6 +163,7 @@ class LauncherClings implements OnClickListener { content.setBackground(bg); } + mLauncher.onLauncherClingShown(); root.addView(cling); if (showWelcome) { @@ -161,12 +181,12 @@ class LauncherClings implements OnClickListener { ObjectAnimator anim; if (TAG_CROP_TOP_AND_SIDES.equals(content.getTag())) { content.setTranslationY(-content.getMeasuredHeight()); - anim = LauncherAnimUtils.ofFloat(content, "translationY", 0); + anim = LauncherAnimUtils.ofFloat(content, View.TRANSLATION_Y, 0); } else { content.setScaleX(0); content.setScaleY(0); - PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1); - PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1); + PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat(View.SCALE_X, 1); + PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat(View.SCALE_Y, 1); anim = LauncherAnimUtils.ofPropertyValuesHolder(content, scaleX, scaleY); } @@ -180,7 +200,12 @@ class LauncherClings implements OnClickListener { @Thunk void dismissLongPressCling() { Runnable dismissCb = new Runnable() { public void run() { - dismissCling(mLauncher.findViewById(R.id.longpress_cling), null, + Runnable cb = new Runnable() { + public void run() { + mLauncher.onLauncherClingDismissed(); + } + }; + dismissCling(mLauncher.findViewById(R.id.longpress_cling), cb, WORKSPACE_CLING_DISMISSED_KEY, DISMISS_CLING_DURATION); } }; diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index afd533016..0f043ce97 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -56,6 +56,7 @@ import com.android.launcher3.compat.PackageInstallerCompat; import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo; import com.android.launcher3.compat.UserHandleCompat; import com.android.launcher3.compat.UserManagerCompat; +import com.android.launcher3.config.ProviderConfig; import com.android.launcher3.model.MigrateFromRestoreTask; import com.android.launcher3.model.WidgetsModel; import com.android.launcher3.util.ComponentKey; @@ -63,6 +64,7 @@ import com.android.launcher3.util.CursorIconInfo; import com.android.launcher3.util.LongArrayMap; import com.android.launcher3.util.ManagedProfileHeuristic; import com.android.launcher3.util.Thunk; +import com.android.launcher3.util.ViewOnDrawExecutor; import java.lang.ref.WeakReference; import java.net.URISyntaxException; @@ -78,6 +80,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map.Entry; import java.util.Set; +import java.util.concurrent.Executor; /** * Maintains in-memory state of the Launcher. It is expected that there should be only one @@ -123,12 +126,6 @@ public class LauncherModel extends BroadcastReceiver @Thunk boolean mWorkspaceLoaded; @Thunk boolean mAllAppsLoaded; - // When we are loading pages synchronously, we can't just post the binding of items on the side - // pages as this delays the rotation process. Instead, we wait for a callback from the first - // draw (in Workspace) to initiate the binding of the remaining side pages. Any time we start - // a normal load, we also clear this set of Runnables. - static final ArrayList<Runnable> mDeferredBindRunnables = new ArrayList<Runnable>(); - /** * Set of runnables to be called on the background thread after the workspace binding * is complete. @@ -187,6 +184,7 @@ public class LauncherModel extends BroadcastReceiver public interface Callbacks { public boolean setLoadOnResume(); public int getCurrentWorkspaceScreen(); + public void clearPendingBinds(); public void startBinding(); public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end, boolean forceAnimateIcons); @@ -211,6 +209,7 @@ public class LauncherModel extends BroadcastReceiver public void bindSearchProviderChanged(); public boolean isAllAppsButtonRank(int rank); public void onPageBoundSynchronously(int page); + public void executeOnNextDraw(ViewOnDrawExecutor executor); public void dumpLogsToLocalData(); } @@ -589,11 +588,6 @@ public class LauncherModel extends BroadcastReceiver "main thread"); } - // Clear any deferred bind runnables - synchronized (mDeferredBindRunnables) { - mDeferredBindRunnables.clear(); - } - // Remove any queued UI runnables mHandler.cancelAll(); // Unbind all the workspace items @@ -652,12 +646,7 @@ public class LauncherModel extends BroadcastReceiver modelShortcut.cellX == shortcut.cellX && modelShortcut.cellY == shortcut.cellY && modelShortcut.spanX == shortcut.spanX && - modelShortcut.spanY == shortcut.spanY && - ((modelShortcut.dropPos == null && shortcut.dropPos == null) || - (modelShortcut.dropPos != null && - shortcut.dropPos != null && - modelShortcut.dropPos[0] == shortcut.dropPos[0] && - modelShortcut.dropPos[1] == shortcut.dropPos[1]))) { + modelShortcut.spanY == shortcut.spanY) { // For all intents and purposes, this is the same object return; } @@ -884,7 +873,7 @@ public class LauncherModel extends BroadcastReceiver } private void assertWorkspaceLoaded() { - if (LauncherAppState.isDogfoodBuild()) { + if (ProviderConfig.IS_DOGFOOD_BUILD) { synchronized (mLock) { if (!mHasLoaderCompletedOnce || (mLoaderTask != null && mLoaderTask.mIsLoadingAndBindingWorkspace)) { @@ -1346,14 +1335,16 @@ public class LauncherModel extends BroadcastReceiver // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems InstallShortcutReceiver.enableInstallQueue(); synchronized (mLock) { - // Clear any deferred bind-runnables from the synchronized load process - // We must do this before any loading/binding is scheduled below. - synchronized (mDeferredBindRunnables) { - mDeferredBindRunnables.clear(); - } - // Don't bother to start the thread if we know it's not going to do anything if (mCallbacks != null && mCallbacks.get() != null) { + final Callbacks oldCallbacks = mCallbacks.get(); + // Clear any pending bind-runnables from the synchronized load process. + runOnMainThread(new Runnable() { + public void run() { + oldCallbacks.clearPendingBinds(); + } + }); + // If there is already one running, tell it to stop. stopLoaderLocked(); mLoaderTask = new LoaderTask(mApp.getContext(), loadFlags); @@ -1368,21 +1359,6 @@ public class LauncherModel extends BroadcastReceiver } } - void bindRemainingSynchronousPages() { - // Post the remaining side pages to be loaded - if (!mDeferredBindRunnables.isEmpty()) { - Runnable[] deferredBindRunnables = null; - synchronized (mDeferredBindRunnables) { - deferredBindRunnables = mDeferredBindRunnables.toArray( - new Runnable[mDeferredBindRunnables.size()]); - mDeferredBindRunnables.clear(); - } - for (final Runnable r : deferredBindRunnables) { - mHandler.post(r); - } - } - } - public void stopLoader() { synchronized (mLock) { if (mLoaderTask != null) { @@ -1725,8 +1701,7 @@ public class LauncherModel extends BroadcastReceiver final PackageManager manager = context.getPackageManager(); final boolean isSafeMode = manager.isSafeMode(); final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context); - final boolean isSdCardReady = context.registerReceiver(null, - new IntentFilter(StartupReceiver.SYSTEM_READY)) != null; + final boolean isSdCardReady = Utilities.isBootCompleted(); LauncherAppState app = LauncherAppState.getInstance(); InvariantDeviceProfile profile = app.getInvariantDeviceProfile(); @@ -2307,7 +2282,7 @@ public class LauncherModel extends BroadcastReceiver if (!isSdCardReady && !sPendingPackages.isEmpty()) { context.registerReceiver(new AppsAvailabilityCheck(), - new IntentFilter(StartupReceiver.SYSTEM_READY), + new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, sWorker); } @@ -2457,19 +2432,36 @@ public class LauncherModel extends BroadcastReceiver private void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) { final LauncherAppState app = LauncherAppState.getInstance(); final InvariantDeviceProfile profile = app.getInvariantDeviceProfile(); - // XXX: review this + final int screenCols = profile.numColumns; + final int screenCellCount = profile.numColumns * profile.numRows; Collections.sort(workspaceItems, new Comparator<ItemInfo>() { @Override public int compare(ItemInfo lhs, ItemInfo rhs) { - int cellCountX = (int) profile.numColumns; - int cellCountY = (int) profile.numRows; - int screenOffset = cellCountX * cellCountY; - int containerOffset = screenOffset * (Launcher.SCREEN_COUNT + 1); // +1 hotseat - long lr = (lhs.container * containerOffset + lhs.screenId * screenOffset + - lhs.cellY * cellCountX + lhs.cellX); - long rr = (rhs.container * containerOffset + rhs.screenId * screenOffset + - rhs.cellY * cellCountX + rhs.cellX); - return (int) (lr - rr); + if (lhs.container == rhs.container) { + // Within containers, order by their spatial position in that container + switch ((int) lhs.container) { + case LauncherSettings.Favorites.CONTAINER_DESKTOP: { + long lr = (lhs.screenId * screenCellCount + + lhs.cellY * screenCols + lhs.cellX); + long rr = (rhs.screenId * screenCellCount + + rhs.cellY * screenCols + rhs.cellX); + return (int) (lr - rr); + } + case LauncherSettings.Favorites.CONTAINER_HOTSEAT: { + // We currently use the screen id as the rank + return (int) (lhs.screenId - rhs.screenId); + } + default: + if (ProviderConfig.IS_DOGFOOD_BUILD) { + throw new RuntimeException("Unexpected container type when " + + "sorting workspace items."); + } + return 0; + } + } else { + // Between containers, order by hotseat, desktop + return (int) (lhs.container - rhs.container); + } } }); } @@ -2492,9 +2484,7 @@ public class LauncherModel extends BroadcastReceiver final ArrayList<ItemInfo> workspaceItems, final ArrayList<LauncherAppWidgetInfo> appWidgets, final LongArrayMap<FolderInfo> folders, - ArrayList<Runnable> deferredBindRunnables) { - - final boolean postOnMainThread = (deferredBindRunnables != null); + final Executor executor) { // Bind the workspace items int N = workspaceItems.size(); @@ -2511,13 +2501,7 @@ public class LauncherModel extends BroadcastReceiver } } }; - if (postOnMainThread) { - synchronized (deferredBindRunnables) { - deferredBindRunnables.add(r); - } - } else { - runOnMainThread(r); - } + executor.execute(r); } // Bind the folders @@ -2530,13 +2514,7 @@ public class LauncherModel extends BroadcastReceiver } } }; - if (postOnMainThread) { - synchronized (deferredBindRunnables) { - deferredBindRunnables.add(r); - } - } else { - runOnMainThread(r); - } + executor.execute(r); } // Bind the widgets, one at a time @@ -2551,11 +2529,7 @@ public class LauncherModel extends BroadcastReceiver } } }; - if (postOnMainThread) { - deferredBindRunnables.add(r); - } else { - runOnMainThread(r); - } + executor.execute(r); } } @@ -2633,6 +2607,7 @@ public class LauncherModel extends BroadcastReceiver public void run() { Callbacks callbacks = tryGetCallbacks(oldCallbacks); if (callbacks != null) { + callbacks.clearPendingBinds(); callbacks.startBinding(); } } @@ -2641,28 +2616,20 @@ public class LauncherModel extends BroadcastReceiver bindWorkspaceScreens(oldCallbacks, orderedScreenIds); - // Load items on the current page + Executor mainExecutor = new DeferredMainThreadExecutor(); + // Load items on the current page. bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets, - currentFolders, null); - if (isLoadingSynchronously) { - r = new Runnable() { - public void run() { - Callbacks callbacks = tryGetCallbacks(oldCallbacks); - if (callbacks != null && currentScreen != PagedView.INVALID_RESTORE_PAGE) { - callbacks.onPageBoundSynchronously(currentScreen); - } - } - }; - runOnMainThread(r); - } + currentFolders, mainExecutor); - // Load all the remaining pages (if we are loading synchronously, we want to defer this - // work until after the first render) - synchronized (mDeferredBindRunnables) { - mDeferredBindRunnables.clear(); - } - bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, otherFolders, - (isLoadingSynchronously ? mDeferredBindRunnables : null)); + // In case of isLoadingSynchronously, only bind the first screen, and defer binding the + // remaining screens after first onDraw is called. This ensures that the first screen + // is immediately visible (eg. during rotation) + // In case of !isLoadingSynchronously, bind all pages one after other. + final Executor deferredExecutor = isLoadingSynchronously ? + new ViewOnDrawExecutor(mHandler) : mainExecutor; + + bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, + otherFolders, deferredExecutor); // Tell the workspace that we're done binding items r = new Runnable() { @@ -2692,11 +2659,23 @@ public class LauncherModel extends BroadcastReceiver } }; + deferredExecutor.execute(r); + if (isLoadingSynchronously) { - synchronized (mDeferredBindRunnables) { - mDeferredBindRunnables.add(r); - } - } else { + r = new Runnable() { + public void run() { + Callbacks callbacks = tryGetCallbacks(oldCallbacks); + if (callbacks != null) { + // We are loading synchronously, which means, some of the pages will be + // bound after first draw. Inform the callbacks that page binding is + // not complete, and schedule the remaining pages. + if (currentScreen != PagedView.INVALID_RESTORE_PAGE) { + callbacks.onPageBoundSynchronously(currentScreen); + } + callbacks.executeOnNextDraw((ViewOnDrawExecutor) deferredExecutor); + } + } + }; runOnMainThread(r); } } @@ -3311,7 +3290,7 @@ public class LauncherModel extends BroadcastReceiver .setPackage(pkg), 0); needToRefresh |= widgets != null && !widgets.isEmpty(); } catch (RuntimeException e) { - if (LauncherAppState.isDogfoodBuild()) { + if (ProviderConfig.IS_DOGFOOD_BUILD) { throw e; } // Ignore the crash. We can live with a state widget list. @@ -3368,7 +3347,7 @@ public class LauncherModel extends BroadcastReceiver return results; } } catch (Exception e) { - if (!LauncherAppState.isDogfoodBuild() && + if (!ProviderConfig.IS_DOGFOOD_BUILD && (e.getCause() instanceof TransactionTooLargeException || e.getCause() instanceof DeadObjectException)) { // the returned value may be incomplete and will not be refreshed until the next @@ -3438,7 +3417,7 @@ public class LauncherModel extends BroadcastReceiver List<ResolveInfo> providers = packageManager.queryIntentActivities(shortcutsIntent, 0); sBgShortcutProviders = providers; } catch (RuntimeException e) { - if (!LauncherAppState.isDogfoodBuild() && + if (!ProviderConfig.IS_DOGFOOD_BUILD && (e.getCause() instanceof TransactionTooLargeException || e.getCause() instanceof DeadObjectException)) { /** @@ -3754,6 +3733,14 @@ public class LauncherModel extends BroadcastReceiver } } + @Thunk class DeferredMainThreadExecutor implements Executor { + + @Override + public void execute(Runnable command) { + runOnMainThread(command); + } + } + /** * @return the looper for the worker thread which can be used to start background tasks. */ diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java index 8791e9e57..c6827dae4 100644 --- a/src/com/android/launcher3/LauncherProvider.java +++ b/src/com/android/launcher3/LauncherProvider.java @@ -1113,10 +1113,6 @@ public class LauncherProvider extends ContentProvider { = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX); final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY); - final int uriIndex - = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI); - final int displayModeIndex - = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE); final int profileIndex = c.getColumnIndex(LauncherSettings.Favorites.PROFILE_ID); @@ -1235,9 +1231,6 @@ public class LauncherProvider extends ContentProvider { c.getString(iconResourceIndex)); values.put(LauncherSettings.Favorites.ITEM_TYPE, itemType); values.put(LauncherSettings.Favorites.APPWIDGET_ID, -1); - values.put(LauncherSettings.Favorites.URI, c.getString(uriIndex)); - values.put(LauncherSettings.Favorites.DISPLAY_MODE, - c.getInt(displayModeIndex)); values.put(LauncherSettings.Favorites.PROFILE_ID, userSerialNumber); if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { diff --git a/src/com/android/launcher3/LauncherStateTransitionAnimation.java b/src/com/android/launcher3/LauncherStateTransitionAnimation.java index cdde8c13f..d3af19a9e 100644 --- a/src/com/android/launcher3/LauncherStateTransitionAnimation.java +++ b/src/com/android/launcher3/LauncherStateTransitionAnimation.java @@ -23,15 +23,17 @@ import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; import android.animation.TimeInterpolator; import android.annotation.SuppressLint; +import android.annotation.TargetApi; import android.content.res.Resources; +import android.os.Build; import android.util.Log; import android.view.View; import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; import com.android.launcher3.allapps.AllAppsContainerView; -import com.android.launcher3.util.UiThreadCircularReveal; import com.android.launcher3.util.Thunk; +import com.android.launcher3.util.UiThreadCircularReveal; import com.android.launcher3.widget.WidgetsContainerView; import java.util.HashMap; @@ -162,7 +164,6 @@ public class LauncherStateTransitionAnimation { final boolean animated) { final WidgetsContainerView toView = mLauncher.getWidgetsView(); final View buttonView = mLauncher.getWidgetsButton(); - PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() { @Override public float getMaterialRevealViewFinalAlpha(View revealView) { @@ -175,11 +176,11 @@ public class LauncherStateTransitionAnimation { } /** - * Starts and animation to the workspace from the current overlay view. + * Starts an animation to the workspace from the current overlay view. */ public void startAnimationToWorkspace(final Launcher.State fromState, final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState, - final int toWorkspacePage, final boolean animated, final Runnable onCompleteRunnable) { + final boolean animated, final Runnable onCompleteRunnable) { if (toWorkspaceState != Workspace.State.NORMAL && toWorkspaceState != Workspace.State.SPRING_LOADED && toWorkspaceState != Workspace.State.OVERVIEW) { @@ -187,10 +188,14 @@ public class LauncherStateTransitionAnimation { } if (fromState == Launcher.State.APPS || fromState == Launcher.State.APPS_SPRING_LOADED) { - startAnimationToWorkspaceFromAllApps(fromWorkspaceState, toWorkspaceState, toWorkspacePage, + startAnimationToWorkspaceFromAllApps(fromWorkspaceState, toWorkspaceState, + animated, onCompleteRunnable); + } else if (fromState == Launcher.State.WIDGETS || + fromState == Launcher.State.WIDGETS_SPRING_LOADED) { + startAnimationToWorkspaceFromWidgets(fromWorkspaceState, toWorkspaceState, animated, onCompleteRunnable); } else { - startAnimationToWorkspaceFromWidgets(fromWorkspaceState, toWorkspaceState, toWorkspacePage, + startAnimationToNewWorkspaceState(fromWorkspaceState, toWorkspaceState, animated, onCompleteRunnable); } } @@ -222,7 +227,7 @@ public class LauncherStateTransitionAnimation { // Create the workspace animation. // NOTE: this call apparently also sets the state for the workspace if !animated - Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState, -1, + Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState, animated, layerViews); // Animate the search bar @@ -257,11 +262,11 @@ public class LauncherStateTransitionAnimation { // Create the animators PropertyValuesHolder panelAlpha = - PropertyValuesHolder.ofFloat("alpha", revealViewToAlpha, 1f); + PropertyValuesHolder.ofFloat(View.ALPHA, revealViewToAlpha, 1f); PropertyValuesHolder panelDriftY = - PropertyValuesHolder.ofFloat("translationY", revealViewToYDrift, 0); + PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, revealViewToYDrift, 0); PropertyValuesHolder panelDriftX = - PropertyValuesHolder.ofFloat("translationX", revealViewToXDrift, 0); + PropertyValuesHolder.ofFloat(View.TRANSLATION_X, revealViewToXDrift, 0); ObjectAnimator panelAlphaAndDrift = ObjectAnimator.ofPropertyValuesHolder(revealView, panelAlpha, panelDriftY, panelDriftX); panelAlphaAndDrift.setDuration(revealDuration); @@ -362,7 +367,7 @@ public class LauncherStateTransitionAnimation { if (layerViews.get(v) == BUILD_AND_SET_LAYER) { v.setLayerType(View.LAYER_TYPE_HARDWARE, null); } - if (Utilities.ATLEAST_LOLLIPOP && Utilities.isViewAttachedToWindow(v)) { + if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) { v.buildLayer(); } } @@ -402,11 +407,11 @@ public class LauncherStateTransitionAnimation { } /** - * Starts and animation to the workspace from the apps view. + * Starts an animation to the workspace from the apps view. */ private void startAnimationToWorkspaceFromAllApps(final Workspace.State fromWorkspaceState, - final Workspace.State toWorkspaceState, final int toWorkspacePage, - final boolean animated, final Runnable onCompleteRunnable) { + final Workspace.State toWorkspaceState, final boolean animated, + final Runnable onCompleteRunnable) { AllAppsContainerView appsView = mLauncher.getAppsView(); PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() { @Override @@ -441,17 +446,17 @@ public class LauncherStateTransitionAnimation { }; // Only animate the search bar if animating to spring loaded mode from all apps mCurrentAnimation = startAnimationToWorkspaceFromOverlay(fromWorkspaceState, toWorkspaceState, - toWorkspacePage, mLauncher.getAllAppsButton(), appsView, appsView.getContentView(), + mLauncher.getAllAppsButton(), appsView, appsView.getContentView(), appsView.getRevealView(), appsView.getSearchBarView(), animated, onCompleteRunnable, cb); } /** - * Starts and animation to the workspace from the widgets view. + * Starts an animation to the workspace from the widgets view. */ private void startAnimationToWorkspaceFromWidgets(final Workspace.State fromWorkspaceState, - final Workspace.State toWorkspaceState, final int toWorkspacePage, - final boolean animated, final Runnable onCompleteRunnable) { + final Workspace.State toWorkspaceState, final boolean animated, + final Runnable onCompleteRunnable) { final WidgetsContainerView widgetsView = mLauncher.getWidgetsView(); PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() { @Override @@ -470,16 +475,105 @@ public class LauncherStateTransitionAnimation { } }; mCurrentAnimation = startAnimationToWorkspaceFromOverlay(fromWorkspaceState, - toWorkspaceState, toWorkspacePage, mLauncher.getWidgetsButton(), widgetsView, + toWorkspaceState, mLauncher.getWidgetsButton(), widgetsView, widgetsView.getContentView(), widgetsView.getRevealView(), null, animated, onCompleteRunnable, cb); } /** + * Starts an animation to the workspace from another workspace state, e.g. normal to overview. + */ + private void startAnimationToNewWorkspaceState(final Workspace.State fromWorkspaceState, + final Workspace.State toWorkspaceState, final boolean animated, + final Runnable onCompleteRunnable) { + final View fromWorkspace = mLauncher.getWorkspace(); + final HashMap<View, Integer> layerViews = new HashMap<>(); + final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet(); + final int revealDuration = mLauncher.getResources() + .getInteger(R.integer.config_overlayRevealTime); + + // Cancel the current animation + cancelAnimation(); + + // Create the workspace animation. + // NOTE: this call apparently also sets the state for the workspace if !animated + Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState, + animated, layerViews); + + startWorkspaceSearchBarAnimation(animation, fromWorkspaceState, toWorkspaceState, + animated ? revealDuration : 0, null); + + if (animated) { + if (workspaceAnim != null) { + animation.play(workspaceAnim); + } + dispatchOnLauncherTransitionPrepare(fromWorkspace, animated, true); + + final AnimatorSet stateAnimation = animation; + final Runnable startAnimRunnable = new Runnable() { + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public void run() { + // Check that mCurrentAnimation hasn't changed while + // we waited for a layout/draw pass + if (mCurrentAnimation != stateAnimation) + return; + + dispatchOnLauncherTransitionStart(fromWorkspace, animated, true); + + // Enable all necessary layers + for (View v : layerViews.keySet()) { + if (layerViews.get(v) == BUILD_AND_SET_LAYER) { + v.setLayerType(View.LAYER_TYPE_HARDWARE, null); + } + if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) { + v.buildLayer(); + } + } + stateAnimation.start(); + } + }; + animation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + dispatchOnLauncherTransitionEnd(fromWorkspace, animated, true); + + // Run any queued runnables + if (onCompleteRunnable != null) { + onCompleteRunnable.run(); + } + + // Disable all necessary layers + for (View v : layerViews.keySet()) { + if (layerViews.get(v) == BUILD_AND_SET_LAYER) { + v.setLayerType(View.LAYER_TYPE_NONE, null); + } + } + + // This can hold unnecessary references to views. + cleanupAnimation(); + } + }); + fromWorkspace.post(startAnimRunnable); + mCurrentAnimation = animation; + } else /* if (!animated) */ { + dispatchOnLauncherTransitionPrepare(fromWorkspace, animated, true); + dispatchOnLauncherTransitionStart(fromWorkspace, animated, true); + dispatchOnLauncherTransitionEnd(fromWorkspace, animated, true); + + // Run any queued runnables + if (onCompleteRunnable != null) { + onCompleteRunnable.run(); + } + + mCurrentAnimation = null; + } + } + + /** * Creates and starts a new animation to the workspace. */ private AnimatorSet startAnimationToWorkspaceFromOverlay(final Workspace.State fromWorkspaceState, - final Workspace.State toWorkspaceState, final int toWorkspacePage, final View buttonView, + final Workspace.State toWorkspaceState, final View buttonView, final View fromView, final View contentView, final View revealView, final View overlaySearchBarView, final boolean animated, final Runnable onCompleteRunnable, final PrivateTransitionCallbacks pCb) { @@ -503,7 +597,7 @@ public class LauncherStateTransitionAnimation { // Create the workspace animation. // NOTE: this call apparently also sets the state for the workspace if !animated Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState, - toWorkspacePage, animated, layerViews); + animated, layerViews); // Animate the search bar startWorkspaceSearchBarAnimation(animation, fromWorkspaceState, toWorkspaceState, @@ -657,6 +751,7 @@ public class LauncherStateTransitionAnimation { final AnimatorSet stateAnimation = animation; final Runnable startAnimRunnable = new Runnable() { + @TargetApi(Build.VERSION_CODES.LOLLIPOP) public void run() { // Check that mCurrentAnimation hasn't changed while // we waited for a layout/draw pass @@ -671,7 +766,7 @@ public class LauncherStateTransitionAnimation { if (layerViews.get(v) == BUILD_AND_SET_LAYER) { v.setLayerType(View.LAYER_TYPE_HARDWARE, null); } - if (Utilities.ATLEAST_LOLLIPOP && Utilities.isViewAttachedToWindow(v)) { + if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) { v.buildLayer(); } } @@ -681,7 +776,7 @@ public class LauncherStateTransitionAnimation { fromView.post(startAnimRunnable); return animation; - } else { + } else /* if (!(animated && initialized)) */ { fromView.setVisibility(View.GONE); dispatchOnLauncherTransitionPrepare(fromView, animated, true); dispatchOnLauncherTransitionStart(fromView, animated, true); diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java index 05f0a0553..e4d644884 100644 --- a/src/com/android/launcher3/PagedView.java +++ b/src/com/android/launcher3/PagedView.java @@ -18,10 +18,10 @@ package com.android.launcher3; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; import android.animation.LayoutTransition; import android.animation.ObjectAnimator; import android.animation.TimeInterpolator; +import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.Context; import android.content.res.TypedArray; @@ -47,10 +47,8 @@ import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.Interpolator; - import com.android.launcher3.util.LauncherEdgeEffect; import com.android.launcher3.util.Thunk; - import java.util.ArrayList; /** @@ -65,9 +63,8 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc // the min drag distance for a fling to register, to prevent random page shifts private static final int MIN_LENGTH_FOR_FLING = 25; - protected static final int PAGE_SNAP_ANIMATION_DURATION = 750; + public static final int PAGE_SNAP_ANIMATION_DURATION = 750; protected static final int SLOW_PAGE_SNAP_ANIMATION_DURATION = 950; - protected static final float NANOTIME_DIV = 1000000000.0f; private static final float RETURN_TO_ORIGINAL_PAGE_THRESHOLD = 0.33f; // The page is moved more than halfway, automatically move to the next page on touch up. @@ -87,25 +84,19 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc private int mFreeScrollMinScrollX = -1; private int mFreeScrollMaxScrollX = -1; - static final int AUTOMATIC_PAGE_SPACING = -1; - protected int mFlingThresholdVelocity; protected int mMinFlingVelocity; protected int mMinSnapVelocity; - protected float mDensity; - protected float mSmoothingTime; - protected float mTouchX; - protected boolean mFirstLayout = true; private int mNormalChildHeight; protected int mCurrentPage; protected int mRestorePage = INVALID_RESTORE_PAGE; - protected int mChildCountOnLastLayout; + private int mChildCountOnLastLayout; protected int mNextPage = INVALID_PAGE; - protected int mMaxScrollX; + private int mMaxScrollX; protected LauncherScroller mScroller; private Interpolator mDefaultInterpolator; private VelocityTracker mVelocityTracker; @@ -117,10 +108,10 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc private float mDownMotionY; private float mDownScrollX; private float mDragViewBaselineLeft; - protected float mLastMotionX; - protected float mLastMotionXRemainder; - protected float mLastMotionY; - protected float mTotalMotionX; + private float mLastMotionX; + private float mLastMotionXRemainder; + private float mLastMotionY; + private float mTotalMotionX; private int mLastScreenCenter = -1; private boolean mCancelTap; @@ -133,23 +124,17 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc protected final static int TOUCH_STATE_NEXT_PAGE = 3; protected final static int TOUCH_STATE_REORDERING = 4; - protected final static float ALPHA_QUANTIZE_LEVEL = 0.0001f; - protected int mTouchState = TOUCH_STATE_REST; - protected boolean mForceScreenScrolled = false; + private boolean mForceScreenScrolled = false; protected OnLongClickListener mLongClickListener; protected int mTouchSlop; private int mMaximumVelocity; - protected int mPageLayoutWidthGap; - protected int mPageLayoutHeightGap; protected int mCellCountX = 0; protected int mCellCountY = 0; - protected boolean mCenterPagesVertically; protected boolean mAllowOverScroll = true; protected int[] mTempVisiblePagesRange = new int[2]; - protected boolean mForceDrawAllChildrenNextFrame; protected static final int INVALID_POINTER = -1; @@ -180,7 +165,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc private float mMinScale = 1f; private boolean mUseMinScale = false; - protected View mDragView; + @Thunk View mDragView; private Runnable mSidePageHoverRunnable; @Thunk int mSidePageHoverIndex = -1; // This variable's scope is only for the duration of startReordering() and endReordering() @@ -189,7 +174,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc // animation after endReordering() private boolean mIsReordering; // The runnable that settles the page after snapToPage and animateDragViewToOriginalPosition - private int NUM_ANIMATIONS_RUNNING_BEFORE_ZOOM_OUT = 2; + private static final int NUM_ANIMATIONS_RUNNING_BEFORE_ZOOM_OUT = 2; private int mPostReorderingPreZoomInRemainingAnimationCount; private Runnable mPostReorderingPreZoomInRunnable; @@ -223,11 +208,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PagedView, defStyle, 0); - - mPageLayoutWidthGap = a.getDimensionPixelSize( - R.styleable.PagedView_pageLayoutWidthGap, 0); - mPageLayoutHeightGap = a.getDimensionPixelSize( - R.styleable.PagedView_pageLayoutHeightGap, 0); mPageIndicatorViewId = a.getResourceId(R.styleable.PagedView_pageIndicator, -1); a.recycle(); @@ -243,16 +223,15 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc mScroller = new LauncherScroller(getContext()); setDefaultInterpolator(new ScrollInterpolator()); mCurrentPage = 0; - mCenterPagesVertically = true; final ViewConfiguration configuration = ViewConfiguration.get(getContext()); mTouchSlop = configuration.getScaledPagingTouchSlop(); mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); - mDensity = getResources().getDisplayMetrics().density; - mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * mDensity); - mMinFlingVelocity = (int) (MIN_FLING_VELOCITY * mDensity); - mMinSnapVelocity = (int) (MIN_SNAP_VELOCITY * mDensity); + float density = getResources().getDisplayMetrics().density; + mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * density); + mMinFlingVelocity = (int) (MIN_FLING_VELOCITY * density); + mMinSnapVelocity = (int) (MIN_SNAP_VELOCITY * density); setOnHierarchyChangeListener(this); setWillNotDraw(false); } @@ -393,7 +372,10 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } /** - * Returns the index of the currently displayed page. + * Returns the index of the currently displayed page. When in free scroll mode, this is the page + * that the user was on before entering free scroll mode (e.g. the home screen page they + * long-pressed on to enter the overview). Try using {@link #getPageNearestToCenterOfScreen()} + * to get the page the user is currently scrolling over. */ public int getCurrentPage() { return mCurrentPage; @@ -402,7 +384,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc /** * Returns the index of page to be shown immediately afterwards. */ - int getNextPage() { + public int getNextPage() { return (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage; } @@ -459,7 +441,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc Math.min(newPage, mTempVisiblePagesRange[1])); } // Ensure that it is clamped by the actual set of children in all cases - validatedPage = Math.max(0, Math.min(validatedPage, getPageCount() - 1)); + validatedPage = Utilities.boundInRange(validatedPage, 0, getPageCount() - 1); return validatedPage; } @@ -606,9 +588,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc super.scrollTo(x, y); } - mTouchX = x; - mSmoothingTime = System.nanoTime() / NANOTIME_DIV; - // Update the last motion events when scrolling if (isReordering(true)) { float[] p = mapPointFromParentToView(this, mParentDownMotionX, mParentDownMotionY); @@ -836,6 +815,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc setMeasuredDimension(scaledWidthSize, scaledHeightSize); } + @SuppressLint("DrawAllocation") @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { if (getChildCount() == 0) { @@ -874,9 +854,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc childTop = offsetY; } else { childTop = offsetY + getPaddingTop() + mInsets.top; - if (mCenterPagesVertically) { - childTop += (getViewportHeight() - mInsets.top - mInsets.bottom - verticalPadding - child.getMeasuredHeight()) / 2; - } + childTop += (getViewportHeight() - mInsets.top - mInsets.bottom - verticalPadding - child.getMeasuredHeight()) / 2; } final int childWidth = child.getMeasuredWidth(); @@ -1064,11 +1042,10 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc if (pageCount > 0) { int viewportWidth = getViewportWidth(); - int curScreen = 0; + int lastVisiblePageIndex = 0; - int count = getChildCount(); - for (int i = 0; i < count; i++) { - View currPage = getPageAt(i); + for (int currPageIndex = 0; currPageIndex < pageCount; currPageIndex++) { + View currPage = getPageAt(currPageIndex); sTmpIntPoint[0] = 0; Utilities.getDescendantCoordRelativeToParent(currPage, this, sTmpIntPoint, false); @@ -1089,13 +1066,13 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc break; } } - curScreen = i; if (range[0] < 0) { - range[0] = curScreen; + range[0] = currPageIndex; } + lastVisiblePageIndex = currPageIndex; } - range[1] = curScreen; + range[1] = lastVisiblePageIndex; } else { range[0] = -1; range[1] = -1; @@ -1136,8 +1113,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc for (int i = pageCount - 1; i >= 0; i--) { final View v = getPageAt(i); if (v == mDragView) continue; - if (mForceDrawAllChildrenNextFrame || - (leftScreen <= i && i <= rightScreen && shouldDrawChild(v))) { + if (leftScreen <= i && i <= rightScreen && shouldDrawChild(v)) { drawChild(canvas, v, drawingTime); } } @@ -1146,7 +1122,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc drawChild(canvas, mDragView, drawingTime); } - mForceDrawAllChildrenNextFrame = false; canvas.restore(); } } @@ -1450,8 +1425,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc mTotalMotionX += Math.abs(mLastMotionX - x); mLastMotionX = x; mLastMotionXRemainder = 0; - mTouchX = getViewportOffsetX() + getScrollX(); - mSmoothingTime = System.nanoTime() / NANOTIME_DIV; onScrollInteractionBegin(); pageBeginMoving(); } @@ -1650,8 +1623,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc // keep the remainder because we are actually testing if we've moved from the last // scrolled position (which is discrete). if (Math.abs(deltaX) >= 1.0f) { - mTouchX += deltaX; - mSmoothingTime = System.nanoTime() / NANOTIME_DIV; scrollBy((int) deltaX, 0); mLastMotionX = x; mLastMotionXRemainder = deltaX - (int) deltaX; @@ -1711,16 +1682,14 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc // Animate the view translation from its old position to its new // position - AnimatorSet anim = (AnimatorSet) v.getTag(ANIM_TAG_KEY); + ObjectAnimator anim = (ObjectAnimator) v.getTag(); if (anim != null) { anim.cancel(); } v.setTranslationX(oldX - newX); - anim = new AnimatorSet(); + anim = LauncherAnimUtils.ofFloat(v, View.TRANSLATION_X, 0); anim.setDuration(REORDERING_REORDER_REPOSITION_DURATION); - anim.playTogether( - ObjectAnimator.ofFloat(v, "translationX", 0f)); anim.start(); v.setTag(anim); } @@ -2170,13 +2139,12 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc // Animate the drag view back to the original position private void animateDragViewToOriginalPosition() { if (mDragView != null) { - AnimatorSet anim = new AnimatorSet(); - anim.setDuration(REORDERING_DROP_REPOSITION_DURATION); - anim.playTogether( - ObjectAnimator.ofFloat(mDragView, "translationX", 0f), - ObjectAnimator.ofFloat(mDragView, "translationY", 0f), - ObjectAnimator.ofFloat(mDragView, "scaleX", 1f), - ObjectAnimator.ofFloat(mDragView, "scaleY", 1f)); + Animator anim = new LauncherViewPropertyAnimator(mDragView) + .translationX(0) + .translationY(0) + .scaleX(1) + .scaleY(1) + .setDuration(REORDERING_DROP_REPOSITION_DURATION); anim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { @@ -2273,9 +2241,8 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc animateDragViewToOriginalPosition(); } - private static final int ANIM_TAG_KEY = 100; - /* Accessibility */ + @SuppressWarnings("deprecation") @TargetApi(Build.VERSION_CODES.LOLLIPOP) @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { @@ -2335,7 +2302,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } protected String getCurrentPageDescription() { - return String.format(getContext().getString(R.string.default_scroll_format), + return getContext().getString(R.string.default_scroll_format, getNextPage() + 1, getChildCount()); } diff --git a/src/com/android/launcher3/SearchDropTargetBar.java b/src/com/android/launcher3/SearchDropTargetBar.java index 772a334b9..878a474e6 100644 --- a/src/com/android/launcher3/SearchDropTargetBar.java +++ b/src/com/android/launcher3/SearchDropTargetBar.java @@ -23,16 +23,15 @@ import android.graphics.Rect; import android.util.AttributeSet; import android.view.View; import android.view.accessibility.AccessibilityManager; -import android.view.animation.AccelerateInterpolator; -import android.widget.FrameLayout; +import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.util.Thunk; /* - * Ths bar will manage the transition between the QSB search bar and the delete drop - * targets so that each of the individual IconDropTargets don't have to. + * This bar will manage the transition between the QSB search bar and the delete/uninstall drop + * targets so that each of the individual ButtonDropTargets don't have to. */ -public class SearchDropTargetBar extends FrameLayout implements DragController.DragListener { +public class SearchDropTargetBar extends BaseDropTargetBar { /** The different states that the search bar space can be in. */ public enum State { @@ -57,21 +56,13 @@ public class SearchDropTargetBar extends FrameLayout implements DragController.D } } - private static int DEFAULT_DRAG_FADE_DURATION = 175; - private LauncherViewPropertyAnimator mDropTargetBarAnimator; private LauncherViewPropertyAnimator mQSBSearchBarAnimator; - private static final AccelerateInterpolator sAccelerateInterpolator = - new AccelerateInterpolator(); private State mState = State.SEARCH_BAR; @Thunk View mQSB; - @Thunk View mDropTargetBar; - private boolean mDeferOnDragEnd = false; - @Thunk boolean mAccessibilityEnabled = false; // Drop targets - private ButtonDropTarget mInfoDropTarget; private ButtonDropTarget mDeleteDropTarget; private ButtonDropTarget mUninstallDropTarget; @@ -83,61 +74,48 @@ public class SearchDropTargetBar extends FrameLayout implements DragController.D super(context, attrs, defStyle); } + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + // Get the individual components + mDeleteDropTarget = (ButtonDropTarget) mDropTargetBar.findViewById(R.id.delete_target_text); + mUninstallDropTarget = (ButtonDropTarget) mDropTargetBar + .findViewById(R.id.uninstall_target_text); + + mDeleteDropTarget.setDropTargetBar(this); + mUninstallDropTarget.setDropTargetBar(this); + } + + @Override public void setup(Launcher launcher, DragController dragController) { dragController.addDragListener(this); dragController.setFlingToDeleteDropTarget(mDeleteDropTarget); - dragController.addDragListener(mInfoDropTarget); dragController.addDragListener(mDeleteDropTarget); dragController.addDragListener(mUninstallDropTarget); - dragController.addDropTarget(mInfoDropTarget); dragController.addDropTarget(mDeleteDropTarget); dragController.addDropTarget(mUninstallDropTarget); - mInfoDropTarget.setLauncher(launcher); mDeleteDropTarget.setLauncher(launcher); mUninstallDropTarget.setLauncher(launcher); } @Override - protected void onFinishInflate() { - super.onFinishInflate(); + public void showDropTarget() { + animateToState(State.DROP_TARGET, DEFAULT_DRAG_FADE_DURATION); + } - // Get the individual components - mDropTargetBar = findViewById(R.id.drag_target_bar); - mInfoDropTarget = (ButtonDropTarget) mDropTargetBar.findViewById(R.id.info_target_text); - mDeleteDropTarget = (ButtonDropTarget) mDropTargetBar.findViewById(R.id.delete_target_text); - mUninstallDropTarget = (ButtonDropTarget) mDropTargetBar.findViewById(R.id.uninstall_target_text); - - mInfoDropTarget.setSearchDropTargetBar(this); - mDeleteDropTarget.setSearchDropTargetBar(this); - mUninstallDropTarget.setSearchDropTargetBar(this); - - // Create the various fade animations - mDropTargetBar.setAlpha(0f); - mDropTargetBarAnimator = new LauncherViewPropertyAnimator(mDropTargetBar); - mDropTargetBarAnimator.setInterpolator(sAccelerateInterpolator); - mDropTargetBarAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - // Ensure that the view is visible for the animation - mDropTargetBar.setVisibility(View.VISIBLE); - } - - @Override - public void onAnimationEnd(Animator animation) { - if (mDropTargetBar != null) { - AlphaUpdateListener.updateVisibility(mDropTargetBar, mAccessibilityEnabled); - } - } - }); + @Override + public void hideDropTarget() { + animateToState(State.SEARCH_BAR, DEFAULT_DRAG_FADE_DURATION); } public void setQsbSearchBar(View qsb) { mQSB = qsb; if (mQSB != null) { - // Update the search ber animation + // Update the search bar animation mQSBSearchBarAnimator = new LauncherViewPropertyAnimator(mQSB); mQSBSearchBarAnimator.setInterpolator(sAccelerateInterpolator); mQSBSearchBarAnimator.addListener(new AnimatorListenerAdapter() { @@ -182,51 +160,6 @@ public class SearchDropTargetBar extends FrameLayout implements DragController.D } /** - * Convenience method to animate the alpha of a view using hardware layers. - */ - private void animateViewAlpha(LauncherViewPropertyAnimator animator, View v, float alpha, - int duration) { - if (v == null) { - return; - } - - animator.cancel(); - if (Float.compare(v.getAlpha(), alpha) != 0) { - if (duration > 0) { - animator.alpha(alpha).withLayer().setDuration(duration).start(); - } else { - v.setAlpha(alpha); - AlphaUpdateListener.updateVisibility(v, mAccessibilityEnabled); - } - } - } - - /* - * DragController.DragListener implementation - */ - @Override - public void onDragStart(DragSource source, Object info, int dragAction) { - animateToState(State.DROP_TARGET, DEFAULT_DRAG_FADE_DURATION); - } - - /** - * This is called to defer hiding the delete drop target until the drop animation has completed, - * instead of hiding immediately when the drag has ended. - */ - public void deferOnDragEnd() { - mDeferOnDragEnd = true; - } - - @Override - public void onDragEnd() { - if (!mDeferOnDragEnd) { - animateToState(State.SEARCH_BAR, DEFAULT_DRAG_FADE_DURATION); - } else { - mDeferOnDragEnd = false; - } - } - - /** * @return the bounds of the QSB search bar. */ public Rect getSearchBarBounds() { @@ -245,11 +178,11 @@ public class SearchDropTargetBar extends FrameLayout implements DragController.D } } + @Override public void enableAccessibleDrag(boolean enable) { if (mQSB != null) { mQSB.setVisibility(enable ? View.GONE : View.VISIBLE); } - mInfoDropTarget.enableAccessibleDrag(enable); mDeleteDropTarget.enableAccessibleDrag(enable); mUninstallDropTarget.enableAccessibleDrag(enable); } diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java index 157b48a39..20c27735e 100644 --- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java +++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java @@ -18,12 +18,9 @@ package com.android.launcher3; import android.app.WallpaperManager; import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Paint; import android.graphics.Rect; import android.view.View; import android.view.ViewGroup; -import android.view.ViewGroup.LayoutParams; public class ShortcutAndWidgetContainer extends ViewGroup { static final String TAG = "CellLayoutChildren"; @@ -43,7 +40,6 @@ public class ShortcutAndWidgetContainer extends ViewGroup { private int mHeightGap; private int mCountX; - private int mCountY; private Launcher mLauncher; @@ -62,7 +58,6 @@ public class ShortcutAndWidgetContainer extends ViewGroup { mWidthGap = widthGap; mHeightGap = heightGap; mCountX = countX; - mCountY = countY; } public View getChildAt(int x, int y) { @@ -80,24 +75,6 @@ public class ShortcutAndWidgetContainer extends ViewGroup { } @Override - protected void dispatchDraw(Canvas canvas) { - @SuppressWarnings("all") // suppress dead code warning - final boolean debug = false; - if (debug) { - // Debug drawing for hit space - Paint p = new Paint(); - p.setColor(0x6600FF00); - for (int i = getChildCount() - 1; i >= 0; i--) { - final View child = getChildAt(i); - final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); - - canvas.drawRect(lp.x, lp.y, lp.x + lp.width, lp.y + lp.height, p); - } - } - super.dispatchDraw(canvas); - } - - @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int count = getChildCount(); @@ -238,7 +215,6 @@ public class ShortcutAndWidgetContainer extends ViewGroup { } } - @Override protected void setChildrenDrawnWithCacheEnabled(boolean enabled) { super.setChildrenDrawnWithCacheEnabled(enabled); } diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java index 5766cf2f2..a86a2f9a5 100644 --- a/src/com/android/launcher3/ShortcutInfo.java +++ b/src/com/android/launcher3/ShortcutInfo.java @@ -29,7 +29,6 @@ import com.android.launcher3.compat.UserHandleCompat; import com.android.launcher3.compat.UserManagerCompat; import java.util.ArrayList; -import java.util.Arrays; /** * Represents a launchable icon on the workspaces and in folders. @@ -71,7 +70,7 @@ public class ShortcutInfo extends ItemInfo { /** * The intent used to start the application. */ - Intent intent; + public Intent intent; /** * Indicates whether the icon comes from an application's resource (if false) @@ -245,7 +244,7 @@ public class ShortcutInfo extends ItemInfo { return "ShortcutInfo(title=" + title + "intent=" + intent + "id=" + this.id + " type=" + this.itemType + " container=" + this.container + " screen=" + screenId + " cellX=" + cellX + " cellY=" + cellY + " spanX=" + spanX + " spanY=" + spanY - + " dropPos=" + Arrays.toString(dropPos) + " user=" + user + ")"; + + " user=" + user + ")"; } public static void dumpShortcutInfoList(String tag, String label, diff --git a/src/com/android/launcher3/SimpleOnStylusPressListener.java b/src/com/android/launcher3/SimpleOnStylusPressListener.java new file mode 100644 index 000000000..6b97dcee6 --- /dev/null +++ b/src/com/android/launcher3/SimpleOnStylusPressListener.java @@ -0,0 +1,25 @@ +package com.android.launcher3; + +import android.view.MotionEvent; +import android.view.View; + +import com.android.launcher3.StylusEventHelper.StylusButtonListener; + +/** + * Simple listener that performs a long click on the view after a stylus button press. + */ +public class SimpleOnStylusPressListener implements StylusButtonListener { + private View mView; + + public SimpleOnStylusPressListener(View view) { + mView = view; + } + + public boolean onPressed(MotionEvent event) { + return mView.isLongClickable() && mView.performLongClick(); + } + + public boolean onReleased(MotionEvent event) { + return false; + } +}
\ No newline at end of file diff --git a/src/com/android/launcher3/StartupReceiver.java b/src/com/android/launcher3/StartupReceiver.java deleted file mode 100644 index 65f913fdf..000000000 --- a/src/com/android/launcher3/StartupReceiver.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.android.launcher3; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; - -public class StartupReceiver extends BroadcastReceiver { - - static final String SYSTEM_READY = "com.android.launcher3.SYSTEM_READY"; - - @Override - public void onReceive(Context context, Intent intent) { - context.sendStickyBroadcast(new Intent(SYSTEM_READY)); - } -} diff --git a/src/com/android/launcher3/Stats.java b/src/com/android/launcher3/Stats.java index cb0e252b2..4aba1503b 100644 --- a/src/com/android/launcher3/Stats.java +++ b/src/com/android/launcher3/Stats.java @@ -25,6 +25,8 @@ import android.util.Log; import android.view.View; import android.view.ViewParent; +import com.android.launcher3.config.ProviderConfig; + public class Stats { /** @@ -71,7 +73,7 @@ public class Stats { if (provider != null) { provider.fillInLaunchSourceData(sourceData); - } else if (LauncherAppState.isDogfoodBuild()) { + } else if (ProviderConfig.IS_DOGFOOD_BUILD) { throw new RuntimeException("Expected LaunchSourceProvider"); } } diff --git a/src/com/android/launcher3/StylusEventHelper.java b/src/com/android/launcher3/StylusEventHelper.java index e03273a6e..d5fc0fad4 100644 --- a/src/com/android/launcher3/StylusEventHelper.java +++ b/src/com/android/launcher3/StylusEventHelper.java @@ -6,55 +6,82 @@ import android.view.ViewConfiguration; /** * Helper for identifying when a stylus touches a view while the primary stylus button is pressed. - * This can occur in {@value MotionEvent#ACTION_DOWN} or {@value MotionEvent#ACTION_MOVE}. On a - * stylus button press this performs the view's {@link View#performLongClick()} method, if the view - * is long clickable. + * This can occur in {@value MotionEvent#ACTION_DOWN} or {@value MotionEvent#ACTION_MOVE}. */ public class StylusEventHelper { - private boolean mIsButtonPressed; - private View mView; - public StylusEventHelper(View view) { - mView = view; + /** + * Implement this interface to receive callbacks for a stylus button press and release. + */ + public interface StylusButtonListener { + /** + * Called when the stylus button is pressed. + * + * @param event The MotionEvent that the button press occurred for. + * @return Whether the event was handled. + */ + public boolean onPressed(MotionEvent event); + + /** + * Called when the stylus button is released after a button press. This is also called if + * the event is canceled or the stylus is lifted off the screen. + * + * @param event The MotionEvent the button release occurred for. + * @return Whether the event was handled. + */ + public boolean onReleased(MotionEvent event); } + private boolean mIsButtonPressed; + private View mView; + private StylusButtonListener mListener; + private final float mSlop; + /** - * Call this in onTouchEvent method of a view to identify a stylus button press and perform a - * long click (if the view is long clickable). + * Constructs a helper for listening to stylus button presses and releases. Ensure that { + * {@link #onMotionEvent(MotionEvent)} and {@link #onGenericMotionEvent(MotionEvent)} are called on + * the helper to correctly identify stylus events. * - * @param event The event to check for a stylus button press. - * @return Whether a stylus event occurred and was handled. + * @param listener The listener to call for stylus events. + * @param view Optional view associated with the touch events. */ - public boolean checkAndPerformStylusEvent(MotionEvent event) { - final float slop = ViewConfiguration.get(mView.getContext()).getScaledTouchSlop(); - - if (!mView.isLongClickable()) { - // We don't do anything unless the view is long clickable. - return false; + public StylusEventHelper(StylusButtonListener listener, View view) { + mListener = listener; + mView = view; + if (mView != null) { + mSlop = ViewConfiguration.get(mView.getContext()).getScaledTouchSlop(); + } else { + mSlop = ViewConfiguration.getTouchSlop(); } + } + public boolean onMotionEvent(MotionEvent event) { final boolean stylusButtonPressed = isStylusButtonPressed(event); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: - mIsButtonPressed = false; - if (stylusButtonPressed && mView.performLongClick()) { - mIsButtonPressed = true; - return true; + mIsButtonPressed = stylusButtonPressed; + if (mIsButtonPressed) { + return mListener.onPressed(event); } break; case MotionEvent.ACTION_MOVE: - if (Utilities.pointInView(mView, event.getX(), event.getY(), slop)) { - if (!mIsButtonPressed && stylusButtonPressed && mView.performLongClick()) { - mIsButtonPressed = true; - return true; - } else if (mIsButtonPressed && !stylusButtonPressed) { - mIsButtonPressed = false; - } + if (!Utilities.pointInView(mView, event.getX(), event.getY(), mSlop)) { + return false; + } + if (!mIsButtonPressed && stylusButtonPressed) { + mIsButtonPressed = true; + return mListener.onPressed(event); + } else if (mIsButtonPressed && !stylusButtonPressed) { + mIsButtonPressed = false; + return mListener.onReleased(event); } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: - mIsButtonPressed = false; + if (mIsButtonPressed) { + mIsButtonPressed = false; + return mListener.onReleased(event); + } break; } return false; diff --git a/src/com/android/launcher3/ToggleWeightWatcher.java b/src/com/android/launcher3/ToggleWeightWatcher.java deleted file mode 100644 index 33701a2c4..000000000 --- a/src/com/android/launcher3/ToggleWeightWatcher.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.android.launcher3; - -import android.app.Activity; - -public class ToggleWeightWatcher extends Activity { - -} diff --git a/src/com/android/launcher3/UninstallDropTarget.java b/src/com/android/launcher3/UninstallDropTarget.java index 955d4013c..73881610b 100644 --- a/src/com/android/launcher3/UninstallDropTarget.java +++ b/src/com/android/launcher3/UninstallDropTarget.java @@ -8,7 +8,7 @@ import android.os.Bundle; import android.os.UserManager; import android.util.AttributeSet; import android.util.Pair; -import com.android.launcher3.R; + import com.android.launcher3.compat.UserHandleCompat; import com.android.launcher3.util.Thunk; @@ -32,7 +32,7 @@ public class UninstallDropTarget extends ButtonDropTarget { } @Override - protected boolean supportsDrop(DragSource source, Object info) { + protected boolean supportsDrop(DragSource source, ItemInfo info) { return supportsDrop(getContext(), info); } @@ -81,15 +81,18 @@ public class UninstallDropTarget extends ButtonDropTarget { @Override void completeDrop(final DragObject d) { final Pair<ComponentName, Integer> componentInfo = getAppInfoFlags(d.dragInfo); - final UserHandleCompat user = ((ItemInfo) d.dragInfo).user; - if (startUninstallActivity(mLauncher, d.dragInfo)) { + final UserHandleCompat user = d.dragInfo.user; + if (startActivityWithUninstallAffordance(d)) { final Runnable checkIfUninstallWasSuccess = new Runnable() { @Override public void run() { - String packageName = componentInfo.first.getPackageName(); - boolean uninstallSuccessful = !AllAppsList.packageHasActivities( - getContext(), packageName, user); + boolean uninstallSuccessful = false; + if (componentInfo != null) { + String packageName = componentInfo.first.getPackageName(); + uninstallSuccessful = !AllAppsList.packageHasActivities( + getContext(), packageName, user); + } sendUninstallResult(d.dragSource, uninstallSuccessful); } }; @@ -99,9 +102,13 @@ public class UninstallDropTarget extends ButtonDropTarget { } } - public static boolean startUninstallActivity(Launcher launcher, Object info) { + protected boolean startActivityWithUninstallAffordance(DragObject d) { + return startUninstallActivity(mLauncher, d.dragInfo); + } + + public static boolean startUninstallActivity(Launcher launcher, ItemInfo info) { final Pair<ComponentName, Integer> componentInfo = getAppInfoFlags(info); - final UserHandleCompat user = ((ItemInfo) info).user; + final UserHandleCompat user = info.user; return launcher.startApplicationUninstallActivity( componentInfo.first, componentInfo.second, user); } @@ -115,7 +122,7 @@ public class UninstallDropTarget extends ButtonDropTarget { /** * Interface defining an object that can provide uninstallable drag objects. */ - public static interface UninstallSource { + public interface UninstallSource { /** * A pending uninstall operation was complete. diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index adedd33b2..4e93684ed 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -56,8 +56,11 @@ import android.util.TypedValue; import android.view.View; import android.widget.Toast; +import com.android.launcher3.config.ProviderConfig; + import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Locale; import java.util.Set; @@ -116,10 +119,9 @@ public final class Utilities { return Log.isLoggable(propertyName, Log.VERBOSE); } - public static boolean isAllowRotationPrefEnabled(Context context, boolean multiProcess) { + public static boolean isAllowRotationPrefEnabled(Context context) { SharedPreferences sharedPrefs = context.getSharedPreferences( - LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE | (multiProcess ? - Context.MODE_MULTI_PROCESS : 0)); + LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE); boolean allowRotationPref = sharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY, false); return sForceEnableRotation || allowRotationPref; } @@ -128,6 +130,18 @@ public final class Utilities { return sForceEnableRotation || context.getResources().getBoolean(R.bool.allow_rotation); } + public static boolean isNycOrAbove() { + // TODO(vadimt): Replace using reflection with looking at the API version once + // Build.VERSION.SDK_INT gets bumped to 24. b/22942492. + try { + View.class.getDeclaredField("DRAG_FLAG_OPAQUE"); + // View.DRAG_FLAG_OPAQUE doesn't exist in M-release, so it's an indication of N+. + return true; + } catch (NoSuchFieldException e) { + return false; + } + } + public static Bitmap createIconBitmap(Cursor c, int iconIndex, Context context) { byte[] data = c.getBlob(iconIndex); try { @@ -340,15 +354,6 @@ public final class Utilities { localY < (v.getHeight() + slop); } - public static void scaleRect(Rect r, float scale) { - if (scale != 1.0f) { - r.left = (int) (r.left * scale + 0.5f); - r.top = (int) (r.top * scale + 0.5f); - r.right = (int) (r.right * scale + 0.5f); - r.bottom = (int) (r.bottom * scale + 0.5f); - } - } - public static int[] getCenterDeltaInScreenSpace(View v0, View v1, int[] delta) { v0.getLocationInWindow(sLoc0); v1.getLocationInWindow(sLoc1); @@ -369,11 +374,18 @@ public final class Utilities { } public static void scaleRectAboutCenter(Rect r, float scale) { - int cx = r.centerX(); - int cy = r.centerY(); - r.offset(-cx, -cy); - Utilities.scaleRect(r, scale); - r.offset(cx, cy); + if (scale != 1.0f) { + int cx = r.centerX(); + int cy = r.centerY(); + r.offset(-cx, -cy); + + r.left = (int) (r.left * scale + 0.5f); + r.top = (int) (r.top * scale + 0.5f); + r.right = (int) (r.right * scale + 0.5f); + r.bottom = (int) (r.bottom * scale + 0.5f); + + r.offset(cx, cy); + } } public static void startActivityForResultSafely( @@ -516,16 +528,6 @@ public final class Utilities { return null; } - @TargetApi(Build.VERSION_CODES.KITKAT) - public static boolean isViewAttachedToWindow(View v) { - if (ATLEAST_KITKAT) { - return v.isAttachedToWindow(); - } else { - // A proxy call which returns null, if the view is not attached to the window. - return v.getKeyDispatcherState() != null; - } - } - /** * Returns a widget with category {@link AppWidgetProviderInfo#WIDGET_CATEGORY_SEARCHBOX} * provided by the same package which is set to be global search activity. @@ -661,7 +663,7 @@ public final class Utilities { } public static void assertWorkerThread() { - if (LauncherAppState.isDogfoodBuild() && + if (ProviderConfig.IS_DOGFOOD_BUILD && (LauncherModel.sWorkerThread.getThreadId() != Process.myTid())) { throw new IllegalStateException(); } @@ -710,4 +712,27 @@ public final class Utilities { public static String createDbSelectionQuery(String columnName, Iterable<?> values) { return String.format(Locale.ENGLISH, "%s IN (%s)", columnName, TextUtils.join(", ", values)); } + + @SuppressWarnings({"unchecked", "rawtypes"}) + public static boolean isBootCompleted() { + try { + Class clazz = Class.forName("android.os.SystemProperties"); + Method getter = clazz.getDeclaredMethod("get", String.class); + String value = (String) getter.invoke(null, "sys.boot_completed"); + return "1".equals(value); + } catch (Exception e) { + Log.d(TAG, "Unable to read system properties"); + // Assume that boot has completed + return true; + } + } + + /** + * Ensures that a value is within given bounds. Specifically: + * If value is less than lowerBound, return lowerBound; else if value is greater than upperBound, + * return upperBound; else return value unchanged. + */ + public static int boundInRange(int value, int lowerBound, int upperBound) { + return Math.max(lowerBound, Math.min(value, upperBound)); + } } diff --git a/src/com/android/launcher3/WallpaperChangedReceiver.java b/src/com/android/launcher3/WallpaperChangedReceiver.java deleted file mode 100644 index 2d5612f12..000000000 --- a/src/com/android/launcher3/WallpaperChangedReceiver.java +++ /dev/null @@ -1,29 +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.launcher3; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; - -public class WallpaperChangedReceiver extends BroadcastReceiver { - public void onReceive(Context context, Intent data) { - LauncherAppState.setApplicationContext(context.getApplicationContext()); - LauncherAppState appState = LauncherAppState.getInstance(); - appState.onWallpaperChanged(); - } -} diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 0fdc742b6..745a19c80 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -28,12 +28,10 @@ import android.appwidget.AppWidgetHostView; import android.appwidget.AppWidgetProviderInfo; import android.content.ComponentName; import android.content.Context; -import android.content.SharedPreferences; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; -import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Point; import android.graphics.PointF; @@ -50,7 +48,6 @@ import android.util.AttributeSet; import android.util.Log; import android.util.SparseArray; import android.view.Choreographer; -import android.view.Display; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; @@ -68,6 +65,12 @@ import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; import com.android.launcher3.accessibility.LauncherAccessibilityDelegate.AccessibilityDragSource; import com.android.launcher3.accessibility.OverviewScreenAccessibilityDelegate; import com.android.launcher3.compat.UserHandleCompat; +import com.android.launcher3.config.ProviderConfig; +import com.android.launcher3.dragndrop.DragController; +import com.android.launcher3.dragndrop.DragLayer; +import com.android.launcher3.dragndrop.DragScroller; +import com.android.launcher3.dragndrop.DragView; +import com.android.launcher3.dragndrop.SpringLoadedDragController; import com.android.launcher3.util.LongArrayMap; import com.android.launcher3.util.Thunk; import com.android.launcher3.util.WallpaperUtils; @@ -92,13 +95,17 @@ public class Workspace extends PagedView private static boolean ENFORCE_DRAG_EVENT_ORDER = false; - protected static final int SNAP_OFF_EMPTY_SCREEN_DURATION = 400; - protected static final int FADE_EMPTY_SCREEN_DURATION = 150; + private static final int SNAP_OFF_EMPTY_SCREEN_DURATION = 400; + private static final int FADE_EMPTY_SCREEN_DURATION = 150; private static final int ADJACENT_SCREEN_DROP_DURATION = 300; - static final boolean MAP_NO_RECURSE = false; - static final boolean MAP_RECURSE = true; + private static final boolean MAP_NO_RECURSE = false; + private static final boolean MAP_RECURSE = true; + + // The screen id used for the empty screen always present to the right. + public final static long EXTRA_EMPTY_SCREEN_ID = -201; + private final static long CUSTOM_CONTENT_SCREEN_ID = -301; private static final long CUSTOM_CONTENT_GESTURE_DELAY = 200; private long mTouchDownTime = -1; @@ -113,10 +120,6 @@ public class Workspace extends PagedView private ShortcutAndWidgetContainer mDragSourceInternal; - // The screen id used for the empty screen always present to the right. - final static long EXTRA_EMPTY_SCREEN_ID = -201; - private final static long CUSTOM_CONTENT_SCREEN_ID = -301; - @Thunk LongArrayMap<CellLayout> mWorkspaceScreens = new LongArrayMap<>(); @Thunk ArrayList<Long> mScreenOrder = new ArrayList<Long>(); @@ -136,9 +139,6 @@ public class Workspace extends PagedView private int mDragOverX = -1; private int mDragOverY = -1; - static Rect mLandscapeCellLayoutMetrics = null; - static Rect mPortraitCellLayoutMetrics = null; - CustomContentCallbacks mCustomContentCallbacks; boolean mCustomContentShowing; private float mLastCustomContentScrollProgress = -1f; @@ -164,12 +164,11 @@ public class Workspace extends PagedView // These are temporary variables to prevent having to allocate a new object just to // return an (x, y) value from helper functions. Do NOT use them to maintain other state. - private int[] mTempCell = new int[2]; - private int[] mTempPt = new int[2]; - private int[] mTempEstimate = new int[2]; + private static final Rect sTempRect = new Rect(); + private final int[] mTempXY = new int[2]; @Thunk float[] mDragViewVisualCenter = new float[2]; private float[] mTempCellLayoutCenterCoordinates = new float[2]; - private Matrix mTempInverseMatrix = new Matrix(); + private int[] mTempVisiblePagesRange = new int[2]; private SpringLoadedDragController mSpringLoadedDragController; private float mSpringLoadedShrinkFactor; @@ -200,7 +199,6 @@ public class Workspace extends PagedView private boolean mIsSwitchingState = false; boolean mAnimatingViewIntoPlace = false; - boolean mIsDragOccuring = false; boolean mChildrenLayersEnabled = true; private boolean mStripScreensOnPageStopMoving = false; @@ -210,9 +208,6 @@ public class Workspace extends PagedView private HolographicOutlineHelper mOutlineHelper; @Thunk Bitmap mDragOutline = null; - private static final Rect sTempRect = new Rect(); - private final int[] mTempXY = new int[2]; - private int[] mTempVisiblePagesRange = new int[2]; public static final int DRAG_BITMAP_PADDING = 2; private boolean mWorkspaceFadeInAdjacentScreens; @@ -223,7 +218,6 @@ public class Workspace extends PagedView @Thunk Runnable mDelayedResizeRunnable; private Runnable mDelayedSnapToPageRunnable; - private Point mDisplaySize = new Point(); // Variables relating to the creation of user folders by hovering shortcuts over shortcuts private static final int FOLDER_CREATION_TIMEOUT = 0; @@ -277,19 +271,13 @@ public class Workspace extends PagedView boolean mStartedSendingScrollEvents; boolean mShouldSendPageSettled; int mLastOverlaySroll = 0; + private boolean mForceDrawAdjacentPages = false; // Handles workspace state transitions private WorkspaceStateTransitionAnimation mStateTransitionAnimation; private AccessibilityDelegate mPagesAccessibilityDelegate; - private final Runnable mBindPages = new Runnable() { - @Override - public void run() { - mLauncher.getModel().bindRemainingSynchronousPages(); - } - }; - /** * Used to inflate the Workspace from XML. * @@ -381,12 +369,11 @@ public class Workspace extends PagedView } @Override - public void onDragStart(final DragSource source, Object info, int dragAction) { + public void onDragStart(final DragSource source, ItemInfo info, int dragAction) { if (ENFORCE_DRAG_EVENT_ORDER) { enfoceDragParity("onDragStart", 0, 0); } - mIsDragOccuring = true; updateChildrenLayersEnabled(false); mLauncher.lockScreenOrientation(); mLauncher.onInteractionBegin(); @@ -417,7 +404,6 @@ public class Workspace extends PagedView removeExtraEmptyScreen(true, mDragSourceInternal != null); } - mIsDragOccuring = false; updateChildrenLayersEnabled(false); mLauncher.unlockScreenOrientation(false); @@ -445,8 +431,6 @@ public class Workspace extends PagedView setupLayoutTransition(); mWallpaperOffset = new WallpaperOffsetInterpolator(); - Display display = mLauncher.getWindowManager().getDefaultDisplay(); - display.getSize(mDisplaySize); mMaxDistanceForFolderCreation = (0.55f * grid.iconSizePx); @@ -578,6 +562,7 @@ public class Workspace extends PagedView CellLayout customScreen = (CellLayout) mLauncher.getLayoutInflater().inflate(R.layout.workspace_screen, this, false); customScreen.disableDragTarget(); + customScreen.disableJailContent(); mWorkspaceScreens.put(CUSTOM_CONTENT_SCREEN_ID, customScreen); mScreenOrder.add(0, CUSTOM_CONTENT_SCREEN_ID); @@ -1318,12 +1303,12 @@ public class Workspace extends PagedView protected void setWallpaperDimension() { new AsyncTask<Void, Void, Void>() { public Void doInBackground(Void ... args) { - String spKey = LauncherFiles.WALLPAPER_CROP_PREFERENCES_KEY; - SharedPreferences sp = - mLauncher.getSharedPreferences(spKey, Context.MODE_MULTI_PROCESS); - WallpaperUtils.suggestWallpaperDimension(mLauncher.getResources(), - sp, mLauncher.getWindowManager(), mWallpaperManager, - mLauncher.overrideWallpaperDimensions()); + if (Utilities.ATLEAST_KITKAT) { + WallpaperUtils.suggestWallpaperDimension(mLauncher); + } else { + WallpaperUtils.suggestWallpaperDimensionPreK(mLauncher, + mLauncher.overrideWallpaperDimensions()); + } return null; } }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null); @@ -1597,6 +1582,7 @@ public class Workspace extends PagedView setOnClickListener(mLauncher); } mLauncher.getSearchDropTargetBar().enableAccessibleDrag(enable); + mLauncher.getAppInfoDropTargetBar().enableAccessibleDrag(enable); mLauncher.getHotseat().getLayout() .enableAccessibleDrag(enable, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG); } @@ -1727,14 +1713,6 @@ public class Workspace extends PagedView } @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - - // Call back to LauncherModel to finish binding after the first draw - post(mBindPages); - } - - @Override protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { if (!mLauncher.isAppsViewVisible()) { final Folder openFolder = getOpenFolder(); @@ -1860,8 +1838,18 @@ public class Workspace extends PagedView updateChildrenLayersEnabled(false); } + @Override + protected void getVisiblePages(int[] range) { + super.getVisiblePages(range); + if (mForceDrawAdjacentPages) { + // In overview mode, make sure that the two side pages are visible. + range[0] = Utilities.boundInRange(getCurrentPage() - 1, numCustomPages(), range[1]); + range[1] = Utilities.boundInRange(getCurrentPage() + 1, range[0], getPageCount() - 1); + } + } + protected void onWallpaperTap(MotionEvent ev) { - final int[] position = mTempCell; + final int[] position = mTempXY; getLocationOnScreen(position); int pointerIndex = ev.getActionIndex(); @@ -1925,6 +1913,18 @@ public class Workspace extends PagedView } public void onDragStartedWithItem(PendingAddItemInfo info, Bitmap b, boolean clipAlpha) { + // Find a page that has enough space to place this widget (after rearranging/resizing). + // Start at the current page and search right (on LTR) until finding a page with enough + // space. Since an empty screen is the furthest right, a page must be found. + int currentPageInOverview = getPageNearestToCenterOfScreen(); + for (int pageIndex = currentPageInOverview; pageIndex < getPageCount(); pageIndex++) { + CellLayout page = (CellLayout) getPageAt(pageIndex); + if (page.hasReorderSolution(info)) { + setCurrentPage(pageIndex); + break; + } + } + int[] size = estimateItemSize(info, false); // The outline is used to visualize where the item will land if dropped @@ -1980,6 +1980,10 @@ public class Workspace extends PagedView return mState == State.OVERVIEW; } + public void snapToPageFromOverView(int whichPage) { + mStateTransitionAnimation.snapToPageFromOverView(whichPage); + } + int getOverviewModeTranslationY() { DeviceProfile grid = mLauncher.getDeviceProfile(); Rect workspacePadding = grid.getWorkspacePadding(Utilities.isRtl(getResources())); @@ -1995,19 +1999,28 @@ public class Workspace extends PagedView return -workspaceOffsetTopEdge + overviewOffsetTopEdge; } + int getSpringLoadedTranslationY() { + return getOverviewModeTranslationY(); + } + /** * Sets the current workspace {@link State}, returning an animation transitioning the workspace * to that new state. */ - public Animator setStateWithAnimation(State toState, int toPage, boolean animated, + public Animator setStateWithAnimation(State toState, boolean animated, HashMap<View, Integer> layerViews) { // Create the animation to the new state Animator workspaceAnim = mStateTransitionAnimation.getAnimationToState(mState, - toState, toPage, animated, layerViews); + toState, animated, layerViews); // Update the current state mState = toState; updateAccessibilityFlags(); + if (mState == State.OVERVIEW || mState == State.SPRING_LOADED) { + // Redraw pages, as we might want to draw pages which were not visible. + mForceDrawAdjacentPages = true; + invalidate(); // This will call dispatchDraw(), which calls getVisiblePages(). + } return workspaceAnim; } @@ -2080,6 +2093,7 @@ public class Workspace extends PagedView mIsSwitchingState = false; updateChildrenLayersEnabled(false); showCustomContentIfNecessary(); + mForceDrawAdjacentPages = false; } void updateCustomContentVisibility() { @@ -2126,19 +2140,17 @@ public class Workspace extends PagedView * @param padding the horizontal and vertical padding to use when drawing */ private static void drawDragView(View v, Canvas destCanvas, int padding) { - final Rect clipRect = sTempRect; - v.getDrawingRect(clipRect); - - boolean textVisible = false; - destCanvas.save(); if (v instanceof TextView) { Drawable d = getTextViewIcon((TextView) v); Rect bounds = getDrawableBounds(d); - clipRect.set(0, 0, bounds.width() + padding, bounds.height() + padding); destCanvas.translate(padding / 2 - bounds.left, padding / 2 - bounds.top); d.draw(destCanvas); } else { + final Rect clipRect = sTempRect; + v.getDrawingRect(clipRect); + + boolean textVisible = false; if (v instanceof FolderIcon) { // For FolderIcons the text can bleed into the icon area, and so we need to // hide the text completely (which can't be achieved by clipping). @@ -2316,7 +2328,8 @@ public class Workspace extends PagedView icon.clearPressedBackground(); } - if (child.getTag() == null || !(child.getTag() instanceof ItemInfo)) { + Object dragObject = child.getTag(); + if (!(dragObject instanceof ItemInfo)) { String msg = "Drag started with a view that has no tag set. This " + "will cause a crash (issue 11627249) down the line. " + "View: " + child + " tag: " + child.getTag(); @@ -2327,11 +2340,14 @@ public class Workspace extends PagedView mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent(); } - DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(), - DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale, accessible); + DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, + (ItemInfo) dragObject, DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, + dragRect, scale, accessible); dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor()); b.recycle(); + + mLauncher.enterSpringLoadedDragMode(); } public void beginExternalDragShared(View child, DragSource source) { @@ -2364,7 +2380,8 @@ public class Workspace extends PagedView Point dragVisualizeOffset = new Point(-padding.get() / 2, padding.get() / 2); Rect dragRect = new Rect(0, 0, iconSize, iconSize); - if (child.getTag() == null || !(child.getTag() instanceof ItemInfo)) { + Object dragObject = child.getTag(); + if (!(dragObject instanceof ItemInfo)) { String msg = "Drag started with a view that has no tag set. This " + "will cause a crash (issue 11627249) down the line. " + "View: " + child + " tag: " + child.getTag(); @@ -2372,12 +2389,15 @@ public class Workspace extends PagedView } // Start the drag - DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(), - DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale, false); + DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, + (ItemInfo) dragObject, DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, + dragRect, scale, false); dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor()); // Recycle temporary bitmaps tmpB.recycle(); + + mLauncher.enterSpringLoadedDragMode(); } public boolean transitionStateShouldAllowDrop() { @@ -2404,7 +2424,7 @@ public class Workspace extends PagedView if (mLauncher.isHotseatLayout(dropTargetLayout)) { mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter); } else { - mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null); + mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter); } int spanX = 1; @@ -2414,9 +2434,8 @@ public class Workspace extends PagedView spanX = dragCellInfo.spanX; spanY = dragCellInfo.spanY; } else { - final ItemInfo dragInfo = (ItemInfo) d.dragInfo; - spanX = dragInfo.spanX; - spanY = dragInfo.spanY; + spanX = d.dragInfo.spanX; + spanY = d.dragInfo.spanY; } int minSpanX = spanX; @@ -2431,12 +2450,12 @@ public class Workspace extends PagedView mTargetCell); float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell); - if (mCreateUserFolderOnDrop && willCreateUserFolder((ItemInfo) d.dragInfo, + if (mCreateUserFolderOnDrop && willCreateUserFolder(d.dragInfo, dropTargetLayout, mTargetCell, distance, true)) { return true; } - if (mAddToExistingFolderOnDrop && willAddToExistingUserFolder((ItemInfo) d.dragInfo, + if (mAddToExistingFolderOnDrop && willAddToExistingUserFolder(d.dragInfo, dropTargetLayout, mTargetCell, distance)) { return true; } @@ -2502,7 +2521,7 @@ public class Workspace extends PagedView return (aboveShortcut && willBecomeShortcut); } - boolean willAddToExistingUserFolder(Object dragInfo, CellLayout target, int[] targetCell, + boolean willAddToExistingUserFolder(ItemInfo dragInfo, CellLayout target, int[] targetCell, float distance) { if (distance > mMaxDistanceForFolderCreation) return false; View dropOverView = target.getChildAt(targetCell[0], targetCell[1]); @@ -2611,11 +2630,11 @@ public class Workspace extends PagedView if (mLauncher.isHotseatLayout(dropTargetLayout)) { mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter); } else { - mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null); + mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter); } } - int snapScreen = WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE; + int snapScreen = -1; boolean resizeOnDrop = false; if (d.dragSource != this) { final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0], @@ -2658,7 +2677,7 @@ public class Workspace extends PagedView // Aside from the special case where we're dropping a shortcut onto a shortcut, // we need to find the nearest cell location that is vacant - ItemInfo item = (ItemInfo) d.dragInfo; + ItemInfo item = d.dragInfo; int minSpanX = item.spanX; int minSpanY = item.spanY; if (item.minSpanX > 0 && item.minSpanY > 0) { @@ -2696,7 +2715,7 @@ public class Workspace extends PagedView CellLayout parentCell = getParentCellLayoutForView(cell); if (parentCell != null) { parentCell.removeView(cell); - } else if (LauncherAppState.isDogfoodBuild()) { + } else if (ProviderConfig.IS_DOGFOOD_BUILD) { throw new NullPointerException("mDragInfo.cell has null parent"); } addInScreen(cell, container, screenId, mTargetCell[0], mTargetCell[1], @@ -2776,9 +2795,7 @@ public class Workspace extends PagedView animateWidgetDrop(info, parent, d.dragView, onCompleteRunnable, animationType, cell, false); } else { - int duration = snapScreen < 0 ? - WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE : - ADJACENT_SCREEN_DROP_DURATION; + int duration = snapScreen < 0 ? -1 : ADJACENT_SCREEN_DROP_DURATION; mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration, onCompleteRunnable, this); } @@ -2838,49 +2855,6 @@ public class Workspace extends PagedView CellLayout layout = getCurrentDropLayout(); setCurrentDropLayout(layout); setCurrentDragOverlappingLayout(layout); - - if (!workspaceInModalState()) { - mLauncher.getDragLayer().showPageHints(); - } - } - - /** Return a rect that has the cellWidth/cellHeight (left, top), and - * widthGap/heightGap (right, bottom) */ - static Rect getCellLayoutMetrics(Launcher launcher, int orientation) { - LauncherAppState app = LauncherAppState.getInstance(); - InvariantDeviceProfile inv = app.getInvariantDeviceProfile(); - - Display display = launcher.getWindowManager().getDefaultDisplay(); - Point smallestSize = new Point(); - Point largestSize = new Point(); - display.getCurrentSizeRange(smallestSize, largestSize); - int countX = (int) inv.numColumns; - int countY = (int) inv.numRows; - boolean isLayoutRtl = Utilities.isRtl(launcher.getResources()); - if (orientation == CellLayout.LANDSCAPE) { - if (mLandscapeCellLayoutMetrics == null) { - Rect padding = inv.landscapeProfile.getWorkspacePadding(isLayoutRtl); - int width = largestSize.x - padding.left - padding.right; - int height = smallestSize.y - padding.top - padding.bottom; - mLandscapeCellLayoutMetrics = new Rect(); - mLandscapeCellLayoutMetrics.set( - DeviceProfile.calculateCellWidth(width, countX), - DeviceProfile.calculateCellHeight(height, countY), 0, 0); - } - return mLandscapeCellLayoutMetrics; - } else if (orientation == CellLayout.PORTRAIT) { - if (mPortraitCellLayoutMetrics == null) { - Rect padding = inv.portraitProfile.getWorkspacePadding(isLayoutRtl); - int width = smallestSize.x - padding.left - padding.right; - int height = largestSize.y - padding.top - padding.bottom; - mPortraitCellLayoutMetrics = new Rect(); - mPortraitCellLayoutMetrics.set( - DeviceProfile.calculateCellWidth(width, countX), - DeviceProfile.calculateCellHeight(height, countY), 0, 0); - } - return mPortraitCellLayoutMetrics; - } - return null; } @Override @@ -2915,8 +2889,6 @@ public class Workspace extends PagedView setCurrentDragOverlappingLayout(null); mSpringLoadedDragController.cancel(); - - mLauncher.getDragLayer().hidePageHints(); } private void enfoceDragParity(String event, int update, int expectedValue) { @@ -3021,41 +2993,27 @@ public class Workspace extends PagedView * * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's * coordinate space. The argument xy is modified with the return result. - * - * if cachedInverseMatrix is not null, this method will just use that matrix instead of - * computing it itself; we use this to avoid redundant matrix inversions in - * findMatchingPageForDragOver - * */ - void mapPointFromSelfToChild(View v, float[] xy, Matrix cachedInverseMatrix) { + void mapPointFromSelfToChild(View v, float[] xy) { xy[0] = xy[0] - v.getLeft(); xy[1] = xy[1] - v.getTop(); } - boolean isPointInSelfOverHotseat(int x, int y, Rect r) { - if (r == null) { - r = new Rect(); - } - mTempPt[0] = x; - mTempPt[1] = y; - mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempPt, true); - - DeviceProfile grid = mLauncher.getDeviceProfile(); - r = grid.getHotseatRect(); - if (r.contains(mTempPt[0], mTempPt[1])) { - return true; - } - return false; + boolean isPointInSelfOverHotseat(int x, int y) { + mTempXY[0] = x; + mTempXY[1] = y; + mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempXY, true); + return mLauncher.getDeviceProfile().isInHotseatRect(mTempXY[0], mTempXY[1]); } void mapPointFromSelfToHotseatLayout(Hotseat hotseat, float[] xy) { - mTempPt[0] = (int) xy[0]; - mTempPt[1] = (int) xy[1]; - mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempPt, true); - mLauncher.getDragLayer().mapCoordInSelfToDescendent(hotseat.getLayout(), mTempPt); + mTempXY[0] = (int) xy[0]; + mTempXY[1] = (int) xy[1]; + mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempXY, true); + mLauncher.getDragLayer().mapCoordInSelfToDescendent(hotseat.getLayout(), mTempXY); - xy[0] = mTempPt[0]; - xy[1] = mTempPt[1]; + xy[0] = mTempXY[0]; + xy[1] = mTempXY[1]; } /* @@ -3101,10 +3059,7 @@ public class Workspace extends PagedView CellLayout cl = (CellLayout) getChildAt(i); final float[] touchXy = {originX, originY}; - // Transform the touch coordinates to the CellLayout's local coordinates - // If the touch point is within the bounds of the cell layout, we can return immediately - cl.getMatrix().invert(mTempInverseMatrix); - mapPointFromSelfToChild(cl, touchXy, mTempInverseMatrix); + mapPointFromSelfToChild(cl, touchXy); if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() && touchXy[1] >= 0 && touchXy[1] <= cl.getHeight()) { @@ -3146,11 +3101,10 @@ public class Workspace extends PagedView // Skip drag over events while we are dragging over side pages if (mInScrollArea || !transitionStateShouldAllowDrop()) return; - Rect r = new Rect(); CellLayout layout = null; - ItemInfo item = (ItemInfo) d.dragInfo; + ItemInfo item = d.dragInfo; if (item == null) { - if (LauncherAppState.isDogfoodBuild()) { + if (ProviderConfig.IS_DOGFOOD_BUILD) { throw new NullPointerException("DragObject has null info"); } return; @@ -3164,7 +3118,7 @@ public class Workspace extends PagedView // Identify whether we have dragged over a side page if (workspaceInModalState()) { if (mLauncher.getHotseat() != null && !isExternalDragWidget(d)) { - if (isPointInSelfOverHotseat(d.x, d.y, r)) { + if (isPointInSelfOverHotseat(d.x, d.y)) { layout = mLauncher.getHotseat().getLayout(); } } @@ -3187,7 +3141,7 @@ public class Workspace extends PagedView } else { // Test to see if we are over the hotseat otherwise just use the current page if (mLauncher.getHotseat() != null && !isDragWidget(d)) { - if (isPointInSelfOverHotseat(d.x, d.y, r)) { + if (isPointInSelfOverHotseat(d.x, d.y)) { layout = mLauncher.getHotseat().getLayout(); } } @@ -3206,10 +3160,10 @@ public class Workspace extends PagedView if (mLauncher.isHotseatLayout(mDragTargetLayout)) { mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter); } else { - mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter, null); + mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter); } - ItemInfo info = (ItemInfo) d.dragInfo; + ItemInfo info = d.dragInfo; int minSpanX = item.spanX; int minSpanY = item.spanY; @@ -3389,24 +3343,6 @@ public class Workspace extends PagedView } /** - * Add the item specified by dragInfo to the given layout. - * @return true if successful - */ - public boolean addExternalItemToScreen(ItemInfo dragInfo, CellLayout layout) { - if (layout.findCellForSpan(mTempEstimate, dragInfo.spanX, dragInfo.spanY)) { - onDropExternal(dragInfo.dropPos, (ItemInfo) dragInfo, (CellLayout) layout, false); - return true; - } - mLauncher.showOutOfSpaceMessage(mLauncher.isHotseatLayout(layout)); - return false; - } - - private void onDropExternal(int[] touchXY, Object dragInfo, - CellLayout cellLayout, boolean insertAtFirst) { - onDropExternal(touchXY, dragInfo, cellLayout, insertAtFirst, null); - } - - /** * Drop an item that didn't originate on one of the workspace screens. * It may have come from Launcher (e.g. from all apps or customize), or it may have * come from another app altogether. @@ -3414,7 +3350,7 @@ public class Workspace extends PagedView * NOTE: This can also be called when we are outside of a drag event, when we want * to add an item to one of the workspace screens. */ - private void onDropExternal(final int[] touchXY, final Object dragInfo, + private void onDropExternal(final int[] touchXY, final ItemInfo dragInfo, final CellLayout cellLayout, boolean insertAtFirst, DragObject d) { final Runnable exitSpringLoadedRunnable = new Runnable() { @Override @@ -3424,7 +3360,7 @@ public class Workspace extends PagedView } }; - ItemInfo info = (ItemInfo) dragInfo; + ItemInfo info = dragInfo; int spanX = info.spanX; int spanY = info.spanY; if (mDragInfo != null) { @@ -3451,14 +3387,14 @@ public class Workspace extends PagedView cellLayout, mTargetCell); float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell); - if (willCreateUserFolder((ItemInfo) d.dragInfo, cellLayout, mTargetCell, - distance, true) || willAddToExistingUserFolder((ItemInfo) d.dragInfo, - cellLayout, mTargetCell, distance)) { + if (willCreateUserFolder(d.dragInfo, cellLayout, mTargetCell, distance, true) + || willAddToExistingUserFolder( + d.dragInfo, cellLayout, mTargetCell, distance)) { findNearestVacantCell = false; } } - final ItemInfo item = (ItemInfo) d.dragInfo; + final ItemInfo item = d.dragInfo; boolean updateWidgetSize = false; if (findNearestVacantCell) { int minSpanX = item.spanX; @@ -3776,7 +3712,7 @@ public class Workspace extends PagedView mDragInfo.container, mDragInfo.screenId); if (cellLayout != null) { cellLayout.onDropChild(mDragInfo.cell); - } else if (LauncherAppState.isDogfoodBuild()) { + } else if (ProviderConfig.IS_DOGFOOD_BUILD) { throw new RuntimeException("Invalid state: cellLayout == null in " + "Workspace#onDropCompleted. Please file a bug. "); }; @@ -3787,6 +3723,9 @@ public class Workspace extends PagedView } mDragOutline = null; mDragInfo = null; + + mLauncher.exitSpringLoadedDragModeDelayed(success, + Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null); } /** @@ -3796,7 +3735,7 @@ public class Workspace extends PagedView CellLayout parentCell = getParentCellLayoutForView(v); if (parentCell != null) { parentCell.removeView(v); - } else if (LauncherAppState.isDogfoodBuild()) { + } else if (ProviderConfig.IS_DOGFOOD_BUILD) { // When an app is uninstalled using the drop target, we wait until resume to remove // the icon. We also remove all the corresponding items from the workspace at // {@link Launcher#bindComponentsRemoved}. That call can come before or after @@ -3823,29 +3762,6 @@ public class Workspace extends PagedView } } - void updateItemLocationsInDatabase(CellLayout cl) { - int count = cl.getShortcutsAndWidgets().getChildCount(); - - long screenId = getIdForScreen(cl); - int container = Favorites.CONTAINER_DESKTOP; - - if (mLauncher.isHotseatLayout(cl)) { - screenId = -1; - container = Favorites.CONTAINER_HOTSEAT; - } - - for (int i = 0; i < count; i++) { - View v = cl.getShortcutsAndWidgets().getChildAt(i); - ItemInfo info = (ItemInfo) v.getTag(); - // Null check required as the AllApps button doesn't have an item info - if (info != null && info.requiresDbUpdate) { - info.requiresDbUpdate = false; - LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId, info.cellX, - info.cellY, info.spanX, info.spanY); - } - } - } - void saveWorkspaceToDb() { saveWorkspaceScreenToDb((CellLayout) mLauncher.getHotseat().getLayout()); int count = getChildCount(); @@ -3899,7 +3815,7 @@ public class Workspace extends PagedView @Override public boolean supportsAppInfoDropTarget() { - return false; + return true; } @Override @@ -4472,7 +4388,7 @@ public class Workspace extends PagedView private String getPageDescription(int page) { int delta = numCustomPages(); - return String.format(getContext().getString(R.string.workspace_scroll_format), + return getContext().getString(R.string.workspace_scroll_format, page + 1 - delta, getChildCount() - delta); } diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java index 54f63bbd8..d32ce7377 100644 --- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java +++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java @@ -30,6 +30,7 @@ import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.DecelerateInterpolator; +import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.util.Thunk; import java.util.HashMap; @@ -174,7 +175,6 @@ public class WorkspaceStateTransitionAnimation { public static final String TAG = "WorkspaceStateTransitionAnimation"; - public static final int SCROLL_TO_CURRENT_PAGE = -1; @Thunk static final int BACKGROUND_FADE_OUT_DURATION = 350; final @Thunk Launcher mLauncher; @@ -217,14 +217,18 @@ public class WorkspaceStateTransitionAnimation { mWorkspaceFadeInAdjacentScreens = grid.shouldFadeAdjacentWorkspaceScreens(); } + public void snapToPageFromOverView(int whichPage) { + mWorkspace.snapToPage(whichPage, mOverviewTransitionTime, mZoomInInterpolator); + } + public AnimatorSet getAnimationToState(Workspace.State fromState, Workspace.State toState, - int toPage, boolean animated, HashMap<View, Integer> layerViews) { + boolean animated, HashMap<View, Integer> layerViews) { AccessibilityManager am = (AccessibilityManager) mLauncher.getSystemService(Context.ACCESSIBILITY_SERVICE); final boolean accessibilityEnabled = am.isEnabled(); TransitionStates states = new TransitionStates(fromState, toState); int workspaceDuration = getAnimationDuration(states); - animateWorkspace(states, toPage, animated, workspaceDuration, layerViews, + animateWorkspace(states, animated, workspaceDuration, layerViews, accessibilityEnabled); animateBackgroundGradient(states, animated, BACKGROUND_FADE_OUT_DURATION); return mStateAnimator; @@ -263,7 +267,7 @@ public class WorkspaceStateTransitionAnimation { /** * Starts a transition animation for the workspace. */ - private void animateWorkspace(final TransitionStates states, int toPage, final boolean animated, + private void animateWorkspace(final TransitionStates states, final boolean animated, final int duration, final HashMap<View, Integer> layerViews, final boolean accessibilityEnabled) { // Reinitialize animation arrays for the current workspace state @@ -281,8 +285,13 @@ public class WorkspaceStateTransitionAnimation { float finalHotseatAndPageIndicatorAlpha = (states.stateIsNormal || states.stateIsSpringLoaded) ? 1f : 0f; float finalOverviewPanelAlpha = states.stateIsOverview ? 1f : 0f; - float finalWorkspaceTranslationY = states.stateIsOverview || states.stateIsOverviewHidden ? - mWorkspace.getOverviewModeTranslationY() : 0; + + float finalWorkspaceTranslationY = 0; + if (states.stateIsOverview || states.stateIsOverviewHidden) { + finalWorkspaceTranslationY = mWorkspace.getOverviewModeTranslationY(); + } else if (states.stateIsSpringLoaded) { + finalWorkspaceTranslationY = mWorkspace.getSpringLoadedTranslationY(); + } final int childCount = mWorkspace.getChildCount(); final int customPageCount = mWorkspace.numCustomPages(); @@ -303,11 +312,7 @@ public class WorkspaceStateTransitionAnimation { } } - if (toPage == SCROLL_TO_CURRENT_PAGE) { - toPage = mWorkspace.getPageNearestToCenterOfScreen(); - } - mWorkspace.snapToPage(toPage, duration, mZoomInInterpolator); - + int toPage = mWorkspace.getPageNearestToCenterOfScreen(); for (int i = 0; i < childCount; i++) { final CellLayout cl = (CellLayout) mWorkspace.getChildAt(i); boolean isCurrentPage = (i == toPage); @@ -379,7 +384,6 @@ public class WorkspaceStateTransitionAnimation { mNewBackgroundAlphas[i] != 0) { ValueAnimator bgAnim = ObjectAnimator.ofFloat(cl, "backgroundAlpha", mOldBackgroundAlphas[i], mNewBackgroundAlphas[i]); - LauncherAnimUtils.ofFloat(cl, 0f, 1f); bgAnim.setInterpolator(mZoomInInterpolator); bgAnim.setDuration(duration); mStateAnimator.play(bgAnim); @@ -488,8 +492,7 @@ public class WorkspaceStateTransitionAnimation { if (animated) { // These properties refer to the background protection gradient used for AllApps // and Widget tray. - ValueAnimator bgFadeOutAnimation = - LauncherAnimUtils.ofFloat(mWorkspace, startAlpha, finalAlpha); + ValueAnimator bgFadeOutAnimation = ValueAnimator.ofFloat(startAlpha, finalAlpha); bgFadeOutAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java index fe7b25edd..947968587 100644 --- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java +++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java @@ -20,7 +20,6 @@ import com.android.launcher3.AppInfo; import com.android.launcher3.AppWidgetResizeFrame; import com.android.launcher3.CellLayout; import com.android.launcher3.DeleteDropTarget; -import com.android.launcher3.DragController.DragListener; import com.android.launcher3.DragSource; import com.android.launcher3.Folder; import com.android.launcher3.FolderInfo; @@ -36,6 +35,7 @@ import com.android.launcher3.R; import com.android.launcher3.ShortcutInfo; import com.android.launcher3.UninstallDropTarget; import com.android.launcher3.Workspace; +import com.android.launcher3.dragndrop.DragController.DragListener; import com.android.launcher3.util.Thunk; import java.util.ArrayList; @@ -375,7 +375,7 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme @Override - public void onDragStart(DragSource source, Object info, int dragAction) { + public void onDragStart(DragSource source, ItemInfo info, int dragAction) { // No-op } diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java index 564527e28..979a7332b 100644 --- a/src/com/android/launcher3/allapps/AllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java @@ -477,8 +477,6 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc // Start the drag mLauncher.getWorkspace().beginDragShared(v, mIconLastTouchPos, this, false); - // Enter spring loaded mode - mLauncher.enterSpringLoadedDragMode(); return false; } @@ -532,7 +530,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc int currentScreen = mLauncher.getCurrentWorkspaceScreen(); Workspace workspace = (Workspace) target; CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen); - ItemInfo itemInfo = (ItemInfo) d.dragInfo; + ItemInfo itemInfo = d.dragInfo; if (layout != null) { showOutOfSpaceMessage = !layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY); diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java index 1f95133d4..f885567ac 100644 --- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java +++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java @@ -319,7 +319,7 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. } } - private Launcher mLauncher; + @Thunk Launcher mLauncher; private LayoutInflater mLayoutInflater; @Thunk AlphabeticalAppsList mApps; private GridLayoutManager mGridLayoutMgr; @@ -342,9 +342,9 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. // each time the search query changes. private String mMarketSearchMessage; // The intent to send off to the market app, updated each time the search query changes. - private Intent mMarketSearchIntent; + @Thunk Intent mMarketSearchIntent; // The last query that the user entered into the search field - private String mLastSearchQuery; + @Thunk String mLastSearchQuery; // Section drawing @Thunk int mSectionNamesMargin; @@ -414,11 +414,10 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. */ public void setLastSearchQuery(String query) { Resources res = mLauncher.getResources(); - String formatStr = res.getString(R.string.all_apps_no_search_results); mLastSearchQuery = query; - mEmptySearchMessage = String.format(formatStr, query); + mEmptySearchMessage = res.getString(R.string.all_apps_no_search_results, query); if (mMarketAppName != null) { - mMarketSearchMessage = String.format(res.getString(R.string.all_apps_search_market_message), + mMarketSearchMessage = res.getString(R.string.all_apps_search_market_message, mMarketAppName); mMarketSearchIntent = createMarketSearchIntent(query); } diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java index 14e2a1863..09a7d59bf 100644 --- a/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java +++ b/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java @@ -17,17 +17,15 @@ package com.android.launcher3.allapps; import android.content.Context; import android.graphics.Bitmap; -import android.graphics.Canvas; import android.util.AttributeSet; -import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; + import com.android.launcher3.BubbleTextView; import com.android.launcher3.BubbleTextView.BubbleTextShadowHandler; import com.android.launcher3.ClickShadowView; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; -import com.android.launcher3.R; /** * A container for RecyclerView to allow for the click shadow view to be shown behind an icon that diff --git a/src/com/android/launcher3/allapps/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/AllAppsSearchBarController.java index 2b363c0cb..d853d5b8d 100644 --- a/src/com/android/launcher3/allapps/AllAppsSearchBarController.java +++ b/src/com/android/launcher3/allapps/AllAppsSearchBarController.java @@ -15,7 +15,6 @@ */ package com.android.launcher3.allapps; -import android.content.ComponentName; import android.graphics.Rect; import android.view.View; import android.view.ViewGroup; diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java index dac0df12a..cb989e58b 100644 --- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java +++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java @@ -16,13 +16,13 @@ package com.android.launcher3.allapps; import android.content.Context; -import android.support.v7.widget.RecyclerView; import android.util.Log; + import com.android.launcher3.AppInfo; import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherAppState; import com.android.launcher3.compat.AlphabeticIndexCompat; import com.android.launcher3.compat.UserHandleCompat; +import com.android.launcher3.config.ProviderConfig; import com.android.launcher3.model.AppNameComparator; import com.android.launcher3.util.ComponentKey; @@ -186,7 +186,7 @@ public class AlphabeticalAppsList { // The of ordered component names as a result of a search query private ArrayList<ComponentKey> mSearchResults; private HashMap<CharSequence, String> mCachedSectionNames = new HashMap<>(); - private RecyclerView.Adapter mAdapter; + private AllAppsGridAdapter mAdapter; private AlphabeticIndexCompat mIndexer; private AppNameComparator mAppNameComparator; private MergeAlgorithm mMergeAlgorithm; @@ -215,7 +215,7 @@ public class AlphabeticalAppsList { /** * Sets the adapter to notify when this dataset changes. */ - public void setAdapter(RecyclerView.Adapter adapter) { + public void setAdapter(AllAppsGridAdapter adapter) { mAdapter = adapter; } @@ -419,7 +419,7 @@ public class AlphabeticalAppsList { if (info != null) { mPredictedApps.add(info); } else { - if (LauncherAppState.isDogfoodBuild()) { + if (ProviderConfig.IS_DOGFOOD_BUILD) { Log.e(TAG, "Predicted app not found: " + ck.flattenToString(mLauncher)); } } diff --git a/src/com/android/launcher3/compat/AlphabeticIndexCompat.java b/src/com/android/launcher3/compat/AlphabeticIndexCompat.java index ec1fb669f..463278ab4 100644 --- a/src/com/android/launcher3/compat/AlphabeticIndexCompat.java +++ b/src/com/android/launcher3/compat/AlphabeticIndexCompat.java @@ -1,6 +1,7 @@ package com.android.launcher3.compat; import android.content.Context; + import com.android.launcher3.Utilities; import java.lang.reflect.Constructor; @@ -62,6 +63,7 @@ public class AlphabeticIndexCompat extends BaseAlphabeticIndex { private boolean mHasValidAlphabeticIndex; private String mDefaultMiscLabel; + @SuppressWarnings({"unchecked", "rawtypes"}) public AlphabeticIndexCompat(Context context) { super(); try { diff --git a/src/com/android/launcher3/config/ProviderConfig.java b/src/com/android/launcher3/config/ProviderConfig.java index e8930d063..825b43422 100644 --- a/src/com/android/launcher3/config/ProviderConfig.java +++ b/src/com/android/launcher3/config/ProviderConfig.java @@ -19,4 +19,6 @@ package com.android.launcher3.config; public class ProviderConfig { public static final String AUTHORITY = "com.android.launcher3.settings".intern(); + + public static boolean IS_DOGFOOD_BUILD = false; } diff --git a/src/com/android/launcher3/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java index 2191455d5..4a39e6be1 100644 --- a/src/com/android/launcher3/DragController.java +++ b/src/com/android/launcher3/dragndrop/DragController.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.launcher3; +package com.android.launcher3.dragndrop; import android.content.ComponentName; import android.content.Context; @@ -26,6 +26,7 @@ import android.graphics.Rect; import android.os.Handler; import android.os.IBinder; import android.util.Log; +import android.view.DragEvent; import android.view.HapticFeedbackConstants; import android.view.KeyEvent; import android.view.MotionEvent; @@ -34,15 +35,24 @@ import android.view.View; import android.view.ViewConfiguration; import android.view.inputmethod.InputMethodManager; +import com.android.launcher3.DeleteDropTarget; +import com.android.launcher3.DragSource; +import com.android.launcher3.DropTarget; +import com.android.launcher3.ItemInfo; +import com.android.launcher3.Launcher; +import com.android.launcher3.PagedView; +import com.android.launcher3.ShortcutInfo; import com.android.launcher3.util.Thunk; +import com.android.launcher3.R; + import java.util.ArrayList; import java.util.HashSet; /** * Class for initiating a drag within a view or across multiple views. */ -public class DragController { +public class DragController implements DragDriver.EventListener { private static final String TAG = "Launcher.DragController"; /** Indicates the drag is a move. */ @@ -59,9 +69,9 @@ public class DragController { private static final int SCROLL_OUTSIDE_ZONE = 0; private static final int SCROLL_WAITING_IN_ZONE = 1; - static final int SCROLL_NONE = -1; - static final int SCROLL_LEFT = 0; - static final int SCROLL_RIGHT = 1; + public static final int SCROLL_NONE = -1; + public static final int SCROLL_LEFT = 0; + public static final int SCROLL_RIGHT = 1; private static final float MAX_FLING_DEGREES = 35f; @@ -71,12 +81,14 @@ public class DragController { // temporaries to avoid gc thrash private Rect mRectTemp = new Rect(); private final int[] mCoordinatesTemp = new int[2]; - private final boolean mIsRtl; - /** Whether or not we're dragging. */ - private boolean mDragging; + /** + * Drag driver for the current drag/drop operation, or null if there is no active DND operation. + * It's null during accessible drag operations. + */ + private DragDriver mDragDriver = null; - /** Whether or not this is an accessible drag operation */ + /** Whether or not an accessible drag operation is in progress. */ private boolean mIsAccessibleDrag; /** X coordinate of the down event. */ @@ -85,11 +97,6 @@ public class DragController { /** Y coordinate of the down event. */ private int mMotionDownY; - /** the area at the edge of the screen that makes the workspace go left - * or right while you're dragging. - */ - private int mScrollZone; - private DropTarget.DragObject mDragObject; /** Who can receive drop events */ @@ -100,9 +107,6 @@ public class DragController { /** The window token used as the parent for the DragView. */ private IBinder mWindowToken; - /** The view that will be scrolled when dragging to the left and right edges of the screen. */ - private View mScrollView; - private View mMoveTarget; @Thunk DragScroller mDragScroller; @@ -115,12 +119,11 @@ public class DragController { @Thunk int mLastTouch[] = new int[2]; @Thunk long mLastTouchUpTime = -1; - @Thunk int mDistanceSinceScroll = 0; private int mTmpPoint[] = new int[2]; private Rect mDragLayerRect = new Rect(); - protected int mFlingToDeleteThresholdVelocity; + protected final int mFlingToDeleteThresholdVelocity; private VelocityTracker mVelocityTracker; /** @@ -135,34 +138,25 @@ public class DragController { * @param dragAction The drag action: either {@link DragController#DRAG_ACTION_MOVE} * or {@link DragController#DRAG_ACTION_COPY} */ - void onDragStart(DragSource source, Object info, int dragAction); + void onDragStart(DragSource source, ItemInfo info, int dragAction); /** * The drag has ended */ void onDragEnd(); } - + /** * Used to create a new DragLayer from XML. - * - * @param context The application's context. */ public DragController(Launcher launcher) { Resources r = launcher.getResources(); mLauncher = launcher; mHandler = new Handler(); - mScrollZone = r.getDimensionPixelSize(R.dimen.scroll_zone); mVelocityTracker = VelocityTracker.obtain(); - float density = r.getDisplayMetrics().density; mFlingToDeleteThresholdVelocity = - (int) (r.getInteger(R.integer.config_flingToDeleteMinVelocity) * density); - mIsRtl = Utilities.isRtl(r); - } - - public boolean dragging() { - return mDragging; + r.getDimensionPixelSize(R.dimen.drag_flingToDeleteMinVelocity); } /** @@ -178,7 +172,7 @@ public class DragController { * @param dragRegion Coordinates within the bitmap b for the position of item being dragged. * Makes dragging feel more precise, e.g. you can clip out a transparent border */ - public void startDrag(View v, Bitmap bmp, DragSource source, Object dragInfo, + public void startDrag(View v, Bitmap bmp, DragSource source, ItemInfo dragInfo, Rect viewImageBounds, int dragAction, float initialDragViewScale) { int[] loc = mCoordinatesTemp; mLauncher.getDragLayer().getLocationInDragLayer(v, loc); @@ -211,7 +205,7 @@ public class DragController { * @param accessible whether this drag should occur in accessibility mode */ public DragView startDrag(Bitmap b, int dragLayerX, int dragLayerY, - DragSource source, Object dragInfo, int dragAction, Point dragOffset, Rect dragRegion, + DragSource source, ItemInfo dragInfo, int dragAction, Point dragOffset, Rect dragRegion, float initialDragViewScale, boolean accessible) { if (PROFILE_DRAWING_DURING_DRAG) { android.os.Debug.startMethodTracing("Launcher"); @@ -234,8 +228,8 @@ public class DragController { final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left; final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top; - mDragging = true; mIsAccessibleDrag = accessible; + mLastDropTarget = null; mDragObject = new DropTarget.DragObject(); @@ -256,6 +250,10 @@ public class DragController { final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX, registrationY, 0, 0, b.getWidth(), b.getHeight(), initialDragViewScale); + if (!accessible) { + mDragDriver = DragDriver.create(this, dragInfo, dragView); + } + if (dragOffset != null) { dragView.setDragVisualizeOffset(new Point(dragOffset)); } @@ -318,18 +316,18 @@ public class DragController { * </pre> */ public boolean dispatchKeyEvent(KeyEvent event) { - return mDragging; + return mDragDriver != null; } public boolean isDragging() { - return mDragging; + return mDragDriver != null || mIsAccessibleDrag; } /** * Stop dragging without dropping. */ public void cancelDrag() { - if (mDragging) { + if (isDragging()) { if (mLastDropTarget != null) { mLastDropTarget.onDragExit(mDragObject); } @@ -340,6 +338,7 @@ public class DragController { } endDrag(); } + public void onAppsRemoved(final ArrayList<String> packageNames, HashSet<ComponentName> cns) { // Cancel the current drag if we are removing an app that we are dragging if (mDragObject != null) { @@ -363,8 +362,8 @@ public class DragController { } private void endDrag() { - if (mDragging) { - mDragging = false; + if (isDragging()) { + mDragDriver = null; mIsAccessibleDrag = false; clearScrollRunnable(); boolean isDeferred = false; @@ -415,18 +414,66 @@ public class DragController { return mTmpPoint; } - long getLastGestureUpTime() { - if (mDragging) { + public long getLastGestureUpTime() { + if (mDragDriver != null) { return System.currentTimeMillis(); } else { return mLastTouchUpTime; } } - void resetLastGestureUpTime() { + public void resetLastGestureUpTime() { mLastTouchUpTime = -1; } + @Override + public void onDriverDragMove(float x, float y) { + final int[] dragLayerPos = getClampedDragLayerPos(x, y); + + handleMoveEvent(dragLayerPos[0], dragLayerPos[1]); + } + + @Override + public void onDriverDragExitWindow() { + if (mLastDropTarget != null) { + mLastDropTarget.onDragExit(mDragObject); + mLastDropTarget = null; + } + } + + @Override + public void onDriverDragEnd(float x, float y, DropTarget dropTargetOverride) { + final int[] dragLayerPos = getClampedDragLayerPos(x, y); + final int dragLayerX = dragLayerPos[0]; + final int dragLayerY = dragLayerPos[1]; + + DropTarget dropTarget; + PointF vec = null; + + if (dropTargetOverride != null) { + dropTarget = dropTargetOverride; + } else { + vec = isFlingingToDelete(mDragObject.dragSource); + if (!DeleteDropTarget.supportsDrop(mDragObject.dragInfo)) { + vec = null; + } + if (vec != null) { + dropTarget = mFlingToDeleteDropTarget; + } else { + dropTarget = findDropTarget((int) x, (int) y, mCoordinatesTemp); + } + } + + drop(dropTarget, x, y, vec); + + endDrag(); + } + + @Override + public void onDriverDragCancel() { + cancelDrag(); + } + /** * Call this from a drag source view. */ @@ -434,8 +481,8 @@ public class DragController { @SuppressWarnings("all") // suppress dead code warning final boolean debug = false; if (debug) { - Log.d(Launcher.TAG, "DragController.onInterceptTouchEvent " + ev + " mDragging=" - + mDragging); + Log.d(Launcher.TAG, "DragController.onInterceptTouchEvent " + ev + " Dragging=" + + (mDragDriver != null)); } if (mIsAccessibleDrag) { @@ -451,41 +498,39 @@ public class DragController { final int dragLayerY = dragLayerPos[1]; switch (action) { - case MotionEvent.ACTION_MOVE: - break; case MotionEvent.ACTION_DOWN: // Remember location of down touch mMotionDownX = dragLayerX; mMotionDownY = dragLayerY; - mLastDropTarget = null; break; case MotionEvent.ACTION_UP: mLastTouchUpTime = System.currentTimeMillis(); - if (mDragging) { - PointF vec = isFlingingToDelete(mDragObject.dragSource); - if (!DeleteDropTarget.supportsDrop(mDragObject.dragInfo)) { - vec = null; - } - if (vec != null) { - dropOnFlingToDeleteTarget(dragLayerX, dragLayerY, vec); - } else { - drop(dragLayerX, dragLayerY); - } - } - endDrag(); - break; - case MotionEvent.ACTION_CANCEL: - cancelDrag(); break; } - return mDragging; + return mDragDriver != null && mDragDriver.onInterceptTouchEvent(ev); + } + + /** + * Call this from a drag source view. + */ + public boolean onDragEvent(DragEvent event) { + return mDragDriver != null && mDragDriver.onDragEvent(event); + } + + /** + * Call this from a drag view. + */ + public void onDragViewAnimationEnd() { + if (mDragDriver != null) { + mDragDriver.onDragViewAnimationEnd(); + } } /** * Sets the view that should handle move events. */ - void setMoveTarget(View view) { + public void setMoveTarget(View view) { mMoveTarget = view; } @@ -499,7 +544,6 @@ public class DragController { mScrollState = SCROLL_OUTSIDE_ZONE; mScrollRunnable.setDirection(SCROLL_RIGHT); mDragScroller.onExitScrollArea(); - mLauncher.getDragLayer().onExitScrollArea(); } } @@ -513,11 +557,8 @@ public class DragController { mDragObject.y = coordinates[1]; checkTouchMove(dropTarget); - // Check if we are hovering over the scroll areas - mDistanceSinceScroll += Math.hypot(mLastTouch[0] - x, mLastTouch[1] - y); mLastTouch[0] = x; mLastTouch[1] = y; - checkScrollState(x, y); } public void forceTouchMove() { @@ -545,41 +586,11 @@ public class DragController { mLastDropTarget = dropTarget; } - @Thunk void checkScrollState(int x, int y) { - final int slop = ViewConfiguration.get(mLauncher).getScaledWindowTouchSlop(); - final int delay = mDistanceSinceScroll < slop ? RESCROLL_DELAY : SCROLL_DELAY; - final DragLayer dragLayer = mLauncher.getDragLayer(); - final int forwardDirection = mIsRtl ? SCROLL_RIGHT : SCROLL_LEFT; - final int backwardsDirection = mIsRtl ? SCROLL_LEFT : SCROLL_RIGHT; - - if (x < mScrollZone) { - if (mScrollState == SCROLL_OUTSIDE_ZONE) { - mScrollState = SCROLL_WAITING_IN_ZONE; - if (mDragScroller.onEnterScrollArea(x, y, forwardDirection)) { - dragLayer.onEnterScrollArea(forwardDirection); - mScrollRunnable.setDirection(forwardDirection); - mHandler.postDelayed(mScrollRunnable, delay); - } - } - } else if (x > mScrollView.getWidth() - mScrollZone) { - if (mScrollState == SCROLL_OUTSIDE_ZONE) { - mScrollState = SCROLL_WAITING_IN_ZONE; - if (mDragScroller.onEnterScrollArea(x, y, backwardsDirection)) { - dragLayer.onEnterScrollArea(backwardsDirection); - mScrollRunnable.setDirection(backwardsDirection); - mHandler.postDelayed(mScrollRunnable, delay); - } - } - } else { - clearScrollRunnable(); - } - } - /** * Call this from a drag source view. */ public boolean onTouchEvent(MotionEvent ev) { - if (!mDragging || mIsAccessibleDrag) { + if (mDragDriver == null || mIsAccessibleDrag) { return false; } @@ -592,47 +603,18 @@ public class DragController { final int dragLayerY = dragLayerPos[1]; switch (action) { - case MotionEvent.ACTION_DOWN: - // Remember where the motion event started - mMotionDownX = dragLayerX; - mMotionDownY = dragLayerY; - - if ((dragLayerX < mScrollZone) || (dragLayerX > mScrollView.getWidth() - mScrollZone)) { - mScrollState = SCROLL_WAITING_IN_ZONE; - mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY); - } else { - mScrollState = SCROLL_OUTSIDE_ZONE; - } - handleMoveEvent(dragLayerX, dragLayerY); - break; - case MotionEvent.ACTION_MOVE: - handleMoveEvent(dragLayerX, dragLayerY); - break; - case MotionEvent.ACTION_UP: - // Ensure that we've processed a move event at the current pointer location. - handleMoveEvent(dragLayerX, dragLayerY); - mHandler.removeCallbacks(mScrollRunnable); - - if (mDragging) { - PointF vec = isFlingingToDelete(mDragObject.dragSource); - if (!DeleteDropTarget.supportsDrop(mDragObject.dragInfo)) { - vec = null; - } - if (vec != null) { - dropOnFlingToDeleteTarget(dragLayerX, dragLayerY, vec); - } else { - drop(dragLayerX, dragLayerY); - } - } - endDrag(); - break; - case MotionEvent.ACTION_CANCEL: - mHandler.removeCallbacks(mScrollRunnable); - cancelDrag(); - break; + case MotionEvent.ACTION_DOWN: + // Remember where the motion event started + mMotionDownX = dragLayerX; + mMotionDownY = dragLayerY; + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + mHandler.removeCallbacks(mScrollRunnable); + break; } - return true; + return mDragDriver.onTouchEvent(ev); } /** @@ -642,7 +624,6 @@ public class DragController { public void prepareAccessibleDrag(int x, int y) { mMotionDownX = x; mMotionDownY = y; - mLastDropTarget = null; } /** @@ -660,7 +641,7 @@ public class DragController { dropTarget.prepareAccessibilityDrop(); // Perform the drop - drop(location[0], location[1]); + drop(dropTarget, location[0], location[1], null); endDrag(); } @@ -675,64 +656,64 @@ public class DragController { ViewConfiguration config = ViewConfiguration.get(mLauncher); mVelocityTracker.computeCurrentVelocity(1000, config.getScaledMaximumFlingVelocity()); - + PointF vel = new PointF(mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity()); + float theta = MAX_FLING_DEGREES + 1; if (mVelocityTracker.getYVelocity() < mFlingToDeleteThresholdVelocity) { // Do a quick dot product test to ensure that we are flinging upwards - PointF vel = new PointF(mVelocityTracker.getXVelocity(), - mVelocityTracker.getYVelocity()); PointF upVec = new PointF(0f, -1f); - float theta = (float) Math.acos(((vel.x * upVec.x) + (vel.y * upVec.y)) / - (vel.length() * upVec.length())); - if (theta <= Math.toRadians(MAX_FLING_DEGREES)) { - return vel; - } + theta = getAngleBetweenVectors(vel, upVec); + } else if (mLauncher.getDeviceProfile().isVerticalBarLayout() && + mVelocityTracker.getXVelocity() < mFlingToDeleteThresholdVelocity) { + // Remove icon is on left side instead of top, so check if we are flinging to the left. + PointF leftVec = new PointF(-1f, 0f); + theta = getAngleBetweenVectors(vel, leftVec); + } + if (theta <= Math.toRadians(MAX_FLING_DEGREES)) { + return vel; } return null; } - private void dropOnFlingToDeleteTarget(float x, float y, PointF vel) { + private float getAngleBetweenVectors(PointF vec1, PointF vec2) { + return (float) Math.acos(((vec1.x * vec2.x) + (vec1.y * vec2.y)) / + (vec1.length() * vec2.length())); + } + + void drop(DropTarget dropTarget, float x, float y, PointF flingVel) { final int[] coordinates = mCoordinatesTemp; mDragObject.x = coordinates[0]; mDragObject.y = coordinates[1]; - // Clean up dragging on the target if it's not the current fling delete target otherwise, - // start dragging to it. - if (mLastDropTarget != null && mFlingToDeleteDropTarget != mLastDropTarget) { - mLastDropTarget.onDragExit(mDragObject); + // Move dragging to the final target. + if (dropTarget != mLastDropTarget) { + if (mLastDropTarget != null) { + mLastDropTarget.onDragExit(mDragObject); + } + mLastDropTarget = dropTarget; + if (dropTarget != null) { + dropTarget.onDragEnter(mDragObject); + } } - // Drop onto the fling-to-delete target - boolean accepted = false; - mFlingToDeleteDropTarget.onDragEnter(mDragObject); - // We must set dragComplete to true _only_ after we "enter" the fling-to-delete target for - // "drop" mDragObject.dragComplete = true; - mFlingToDeleteDropTarget.onDragExit(mDragObject); - if (mFlingToDeleteDropTarget.acceptDrop(mDragObject)) { - mFlingToDeleteDropTarget.onFlingToDelete(mDragObject, vel); - accepted = true; - } - mDragObject.dragSource.onDropCompleted((View) mFlingToDeleteDropTarget, mDragObject, true, - accepted); - } - - private void drop(float x, float y) { - final int[] coordinates = mCoordinatesTemp; - final DropTarget dropTarget = findDropTarget((int) x, (int) y, coordinates); - mDragObject.x = coordinates[0]; - mDragObject.y = coordinates[1]; + // Drop onto the target. boolean accepted = false; if (dropTarget != null) { - mDragObject.dragComplete = true; dropTarget.onDragExit(mDragObject); if (dropTarget.acceptDrop(mDragObject)) { - dropTarget.onDrop(mDragObject); + if (flingVel != null) { + dropTarget.onFlingToDelete(mDragObject, flingVel); + } else { + dropTarget.onDrop(mDragObject); + } accepted = true; } } - mDragObject.dragSource.onDropCompleted((View) dropTarget, mDragObject, false, accepted); + final View dropTargetAsView = dropTarget instanceof View ? (View) dropTarget : null; + mDragObject.dragSource.onDropCompleted( + dropTargetAsView, mDragObject, flingVel != null, accepted); } private DropTarget findDropTarget(int x, int y, int[] dropCoordinates) { @@ -818,17 +799,6 @@ public class DragController { } } - /** - * Set which view scrolls for touch events near the edge of the screen. - */ - public void setScrollView(View v) { - mScrollView = v; - } - - DragView getDragView() { - return mDragObject.dragView; - } - private class ScrollRunnable implements Runnable { private int mDirection; @@ -843,14 +813,7 @@ public class DragController { mDragScroller.scrollRight(); } mScrollState = SCROLL_OUTSIDE_ZONE; - mDistanceSinceScroll = 0; mDragScroller.onExitScrollArea(); - mLauncher.getDragLayer().onExitScrollArea(); - - if (isDragging()) { - // Check the scroll again so that we can requeue the scroller if necessary - checkScrollState(mLastTouch[0], mLastTouch[1]); - } } } diff --git a/src/com/android/launcher3/dragndrop/DragDriver.java b/src/com/android/launcher3/dragndrop/DragDriver.java new file mode 100644 index 000000000..6e4b430d4 --- /dev/null +++ b/src/com/android/launcher3/dragndrop/DragDriver.java @@ -0,0 +1,250 @@ +/* + * 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.launcher3.dragndrop; + +import com.android.launcher3.AnotherWindowDropTarget; +import com.android.launcher3.DropTarget; +import com.android.launcher3.ItemInfo; +import com.android.launcher3.Utilities; + +import android.content.ClipData; +import android.content.Intent; +import android.graphics.Canvas; +import android.graphics.Point; +import android.view.DragEvent; +import android.view.MotionEvent; +import android.view.View; + +/** + * Base class for driving a drag/drop operation. + */ +public abstract class DragDriver { + protected final EventListener mEventListener; + + public interface EventListener { + void onDriverDragMove(float x, float y); + void onDriverDragExitWindow(); + void onDriverDragEnd(float x, float y, DropTarget dropTargetOverride); + void onDriverDragCancel(); + } + + public DragDriver(EventListener eventListener) { + mEventListener = eventListener; + } + + /** + * Handles ending of the DragView animation. + */ + public abstract void onDragViewAnimationEnd(); + + public boolean onTouchEvent(MotionEvent ev) { + final int action = ev.getAction(); + + switch (action) { + case MotionEvent.ACTION_MOVE: + mEventListener.onDriverDragMove(ev.getX(), ev.getY()); + break; + case MotionEvent.ACTION_UP: + mEventListener.onDriverDragMove(ev.getX(), ev.getY()); + mEventListener.onDriverDragEnd(ev.getX(), ev.getY(), null); + break; + case MotionEvent.ACTION_CANCEL: + mEventListener.onDriverDragCancel(); + break; + } + + return true; + } + + public abstract boolean onDragEvent (DragEvent event); + + + public boolean onInterceptTouchEvent(MotionEvent ev) { + final int action = ev.getAction(); + + switch (action) { + case MotionEvent.ACTION_UP: + mEventListener.onDriverDragEnd(ev.getX(), ev.getY(), null); + break; + case MotionEvent.ACTION_CANCEL: + mEventListener.onDriverDragCancel(); + break; + } + + return true; + } + + public static DragDriver create( + DragController dragController, ItemInfo dragInfo, DragView dragView) { + if (Utilities.isNycOrAbove()) { + return new SystemDragDriver(dragController, dragInfo.getIntent(), dragView); + } else { + return new InternalDragDriver(dragController); + } + } + +}; + +/** + * Class for driving a system (i.e. framework) drag/drop operation. + */ +class SystemDragDriver extends DragDriver { + /** Intent associated with the drag operation, or null is there no associated intent. */ + private final Intent mDragIntent; + + private final DragView mDragView; + boolean mIsFrameworkDragActive = false; + boolean mReceivedDropEvent = false; + float mLastX = 0; + float mLastY = 0; + + public SystemDragDriver(DragController dragController, Intent dragIntent, DragView dragView) { + super(dragController); + mDragIntent = dragIntent; + mDragView = dragView; + } + + private static class ShadowBuilder extends View.DragShadowBuilder { + final DragView mDragView; + + public ShadowBuilder(DragView dragView) { + mDragView = dragView; + } + + @Override + public void onProvideShadowMetrics (Point size, Point touch) { + mDragView.provideDragShadowMetrics(size, touch); + } + + @Override + public void onDrawShadow(Canvas canvas) { + mDragView.drawDragShadow(canvas); + } + }; + + @Override + public void onDragViewAnimationEnd() { + // Clip data for the drag operation. If there is an intent, create an intent-based ClipData, + // which will be passed to a global DND. + // If there is no intent, craft a fake ClipData and start a local DND operation; this + // ClipData will be ignored. + final ClipData dragData = mDragIntent != null ? + ClipData.newIntent("", mDragIntent) : + ClipData.newPlainText("", ""); + + View.DragShadowBuilder shadowBuilder = new ShadowBuilder(mDragView); + // TODO: DND flags are in flux, once settled, use the appropriate constant. + final int flagGlobal = 1 << 0; + final int flagOpaque = 1 << 9; + final int flags = (mDragIntent != null ? flagGlobal : 0) | flagOpaque; + + mIsFrameworkDragActive = true; + + if (!mDragView.startDrag(dragData, shadowBuilder, null, flags)) { + mIsFrameworkDragActive = false; + mEventListener.onDriverDragCancel(); + return; + } + + // Starting from this point, the driver takes over showing the drag shadow, so hiding the + // drag view. + mDragView.setVisibility(View.INVISIBLE); + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + return !mIsFrameworkDragActive && super.onTouchEvent(ev); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + return !mIsFrameworkDragActive && super.onInterceptTouchEvent(ev); + } + + @Override + public boolean onDragEvent (DragEvent event) { + if (!mIsFrameworkDragActive) { + // We are interested only in drag events started by this driver. + return false; + } + + final int action = event.getAction(); + + switch (action) { + case DragEvent.ACTION_DRAG_STARTED: + mLastX = event.getX(); + mLastY = event.getY(); + return true; + + case DragEvent.ACTION_DRAG_ENTERED: + mLastX = event.getX(); + mLastY = event.getY(); + return true; + + case DragEvent.ACTION_DRAG_LOCATION: + mLastX = event.getX(); + mLastY = event.getY(); + mEventListener.onDriverDragMove(event.getX(), event.getY()); + return true; + + case DragEvent.ACTION_DROP: + mLastX = event.getX(); + mLastY = event.getY(); + mReceivedDropEvent = true; + return true; + + case DragEvent.ACTION_DRAG_EXITED: + mLastX = event.getX(); + mLastY = event.getY(); + mEventListener.onDriverDragExitWindow(); + return true; + + case DragEvent.ACTION_DRAG_ENDED: + final boolean dragAccepted = event.getResult(); + final boolean acceptedByAnotherWindow = dragAccepted && !mReceivedDropEvent; + + // When the system drag ends, its drag shadow disappears. Resume showing the drag + // view for the possible final animation. + mDragView.setVisibility(View.VISIBLE); + + final DropTarget dropTargetOverride = acceptedByAnotherWindow ? + new AnotherWindowDropTarget(mDragView.getContext()) : null; + + mEventListener.onDriverDragEnd(mLastX, mLastY, dropTargetOverride); + mIsFrameworkDragActive = false; + return true; + + default: + return false; + } + } +}; + +/** + * Class for driving an internal (i.e. not using framework) drag/drop operation. + */ +class InternalDragDriver extends DragDriver { + public InternalDragDriver(DragController dragController) { + super(dragController); + } + + @Override + public void onDragViewAnimationEnd() {} + + @Override + public boolean onDragEvent (DragEvent event) { return false; } +}; diff --git a/src/com/android/launcher3/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java index 1c18747c1..adcc12cb6 100644 --- a/src/com/android/launcher3/DragLayer.java +++ b/src/com/android/launcher3/dragndrop/DragLayer.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.launcher3; +package com.android.launcher3.dragndrop; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -28,6 +28,7 @@ import android.graphics.Color; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.util.AttributeSet; +import android.view.DragEvent; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; @@ -39,9 +40,24 @@ import android.view.animation.Interpolator; import android.widget.FrameLayout; import android.widget.TextView; +import com.android.launcher3.AppWidgetResizeFrame; +import com.android.launcher3.CellLayout; +import com.android.launcher3.Folder; +import com.android.launcher3.FolderIcon; +import com.android.launcher3.InsettableFrameLayout; +import com.android.launcher3.ItemInfo; +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherAppWidgetHostView; +import com.android.launcher3.SearchDropTargetBar; +import com.android.launcher3.ShortcutAndWidgetContainer; +import com.android.launcher3.Utilities; +import com.android.launcher3.Workspace; import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; import com.android.launcher3.util.Thunk; +import com.android.launcher3.R; + import java.util.ArrayList; /** @@ -86,15 +102,6 @@ public class DragLayer extends InsettableFrameLayout { // Darkening scrim private float mBackgroundAlpha = 0; - // Related to adjacent page hints - private final Rect mScrollChildPosition = new Rect(); - private boolean mInScrollArea; - private boolean mShowPageHints; - private Drawable mLeftHoverDrawable; - private Drawable mRightHoverDrawable; - private Drawable mLeftHoverDrawableActive; - private Drawable mRightHoverDrawableActive; - private boolean mBlockTouches = false; /** @@ -110,12 +117,7 @@ public class DragLayer extends InsettableFrameLayout { setMotionEventSplittingEnabled(false); setChildrenDrawingOrderEnabled(true); - final Resources res = getResources(); - mLeftHoverDrawable = res.getDrawable(R.drawable.page_hover_left); - mRightHoverDrawable = res.getDrawable(R.drawable.page_hover_right); - mLeftHoverDrawableActive = res.getDrawable(R.drawable.page_hover_left_active); - mRightHoverDrawableActive = res.getDrawable(R.drawable.page_hover_right_active); - mIsRtl = Utilities.isRtl(res); + mIsRtl = Utilities.isRtl(getResources()); } public void setup(Launcher launcher, DragController controller) { @@ -163,6 +165,11 @@ public class DragLayer extends InsettableFrameLayout { if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) { return true; } + + getDescendantRectRelativeToSelf(mLauncher.getAppInfoDropTargetBar(), mHitRect); + if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) { + return true; + } return false; } @@ -322,6 +329,7 @@ public class DragLayer extends InsettableFrameLayout { if (isInAccessibleDrag()) { childrenForAccessibility.add(mLauncher.getSearchDropTargetBar()); + childrenForAccessibility.add(mLauncher.getAppInfoDropTargetBar()); } } else { super.addChildrenForAccessibility(childrenForAccessibility); @@ -375,6 +383,11 @@ public class DragLayer extends InsettableFrameLayout { return mDragController.onTouchEvent(ev); } + @Override + public boolean onDragEvent (DragEvent event) { + return mDragController.onDragEvent(event); + } + /** * Determine the rect of the descendant in this DragLayer's coordinates * @@ -764,7 +777,7 @@ public class DragLayer extends InsettableFrameLayout { // Show the drop view if it was previously hidden mDropView = view; mDropView.cancelAnimation(); - mDropView.resetLayoutParams(); + mDropView.requestLayout(); // Set the anchor view if the page is scrolling if (anchorView != null) { @@ -874,29 +887,6 @@ public class DragLayer extends InsettableFrameLayout { } } - void onEnterScrollArea(int direction) { - mInScrollArea = true; - invalidate(); - } - - void onExitScrollArea() { - mInScrollArea = false; - invalidate(); - } - - void showPageHints() { - mShowPageHints = true; - Workspace workspace = mLauncher.getWorkspace(); - getDescendantRectRelativeToSelf(workspace.getChildAt(workspace.numCustomPages()), - mScrollChildPosition); - invalidate(); - } - - void hidePageHints() { - mShowPageHints = false; - invalidate(); - } - @Override protected void dispatchDraw(Canvas canvas) { // Draw the background below children. @@ -908,41 +898,6 @@ public class DragLayer extends InsettableFrameLayout { super.dispatchDraw(canvas); } - private void drawPageHints(Canvas canvas) { - if (mShowPageHints) { - Workspace workspace = mLauncher.getWorkspace(); - int width = getMeasuredWidth(); - int page = workspace.getNextPage(); - CellLayout leftPage = (CellLayout) workspace.getChildAt(mIsRtl ? page + 1 : page - 1); - CellLayout rightPage = (CellLayout) workspace.getChildAt(mIsRtl ? page - 1 : page + 1); - - if (leftPage != null && leftPage.isDragTarget()) { - Drawable left = mInScrollArea && leftPage.getIsDragOverlapping() ? - mLeftHoverDrawableActive : mLeftHoverDrawable; - left.setBounds(0, mScrollChildPosition.top, - left.getIntrinsicWidth(), mScrollChildPosition.bottom); - left.draw(canvas); - } - if (rightPage != null && rightPage.isDragTarget()) { - Drawable right = mInScrollArea && rightPage.getIsDragOverlapping() ? - mRightHoverDrawableActive : mRightHoverDrawable; - right.setBounds(width - right.getIntrinsicWidth(), - mScrollChildPosition.top, width, mScrollChildPosition.bottom); - right.draw(canvas); - } - } - } - - protected boolean drawChild(Canvas canvas, View child, long drawingTime) { - boolean ret = super.drawChild(canvas, child, drawingTime); - - // We want to draw the page hints above the workspace, but below the drag view. - if (child instanceof Workspace) { - drawPageHints(canvas); - } - return ret; - } - public void setBackgroundAlpha(float alpha) { if (alpha != mBackgroundAlpha) { mBackgroundAlpha = alpha; diff --git a/src/com/android/launcher3/DragScroller.java b/src/com/android/launcher3/dragndrop/DragScroller.java index e261f15d8..165d0b11c 100644 --- a/src/com/android/launcher3/DragScroller.java +++ b/src/com/android/launcher3/dragndrop/DragScroller.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.launcher3; +package com.android.launcher3.dragndrop; /** * Handles scrolling while dragging diff --git a/src/com/android/launcher3/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java index 2acfc6140..88e11fa61 100644 --- a/src/com/android/launcher3/DragView.java +++ b/src/com/android/launcher3/dragndrop/DragView.java @@ -14,11 +14,14 @@ * limitations under the License. */ -package com.android.launcher3; +package com.android.launcher3.dragndrop; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.FloatArrayEvaluator; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; +import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.res.Resources; import android.graphics.Bitmap; @@ -33,8 +36,13 @@ import android.os.Build; import android.view.View; import android.view.animation.DecelerateInterpolator; +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAnimUtils; +import com.android.launcher3.Utilities; import com.android.launcher3.util.Thunk; +import com.android.launcher3.R; + import java.util.Arrays; public class DragView extends View { @@ -45,19 +53,18 @@ public class DragView extends View { private Bitmap mBitmap; private Bitmap mCrossFadeBitmap; @Thunk Paint mPaint; - private int mRegistrationX; - private int mRegistrationY; + private final int mRegistrationX; + private final int mRegistrationY; private Point mDragVisualizeOffset = null; private Rect mDragRegion = null; - private DragLayer mDragLayer = null; + private final DragLayer mDragLayer; + @Thunk final DragController mDragController; private boolean mHasDrawn = false; @Thunk float mCrossFadeProgress = 0f; + private boolean mAnimationCancelled = false; ValueAnimator mAnim; - @Thunk float mOffsetX = 0.0f; - @Thunk float mOffsetY = 0.0f; - private float mInitialScale = 1f; // The intrinsic icon scale factor is the scale factor for a drag icon over the workspace // size. This is ignored for non-icons. private float mIntrinsicIconScale = 1f; @@ -81,7 +88,7 @@ public class DragView extends View { int left, int top, int width, int height, final float initialScale) { super(launcher); mDragLayer = launcher.getDragLayer(); - mInitialScale = initialScale; + mDragController = launcher.getDragController(); final Resources res = getResources(); final float scaleDps = res.getDimensionPixelSize(R.dimen.dragViewScale); @@ -99,11 +106,6 @@ public class DragView extends View { public void onAnimationUpdate(ValueAnimator animation) { final float value = (Float) animation.getAnimatedValue(); - final int deltaX = (int) (-mOffsetX); - final int deltaY = (int) (-mOffsetY); - - mOffsetX += deltaX; - mOffsetY += deltaY; setScaleX(initialScale + (value * (scale - initialScale))); setScaleY(initialScale + (value * (scale - initialScale))); if (sDragAlpha != 1f) { @@ -112,9 +114,15 @@ public class DragView extends View { if (getParent() == null) { animation.cancel(); - } else { - setTranslationX(getTranslationX() + deltaX); - setTranslationY(getTranslationY() + deltaY); + } + } + }); + + mAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (!mAnimationCancelled) { + mDragController.onDragViewAnimationEnd(); } } }); @@ -145,10 +153,6 @@ public class DragView extends View { return mIntrinsicIconScale; } - public float getOffsetY() { - return mOffsetY; - } - public int getDragRegionLeft() { return mDragRegion.left; } @@ -181,30 +185,33 @@ public class DragView extends View { return mDragRegion; } - public float getInitialScale() { - return mInitialScale; + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + setMeasuredDimension(mBitmap.getWidth(), mBitmap.getHeight()); } - public void updateInitialScaleToCurrentScale() { - mInitialScale = getScaleX(); + // Draws drag shadow for system DND. + @SuppressLint("WrongCall") + public void drawDragShadow(Canvas canvas) { + final int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG); + canvas.scale(getScaleX(), getScaleY()); + onDraw(canvas); + canvas.restoreToCount(saveCount); } - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - setMeasuredDimension(mBitmap.getWidth(), mBitmap.getHeight()); + // Provides drag shadow metrics for system DND. + public void provideDragShadowMetrics(Point size, Point touch) { + size.set((int)(mBitmap.getWidth() * getScaleX()), (int)(mBitmap.getHeight() * getScaleY())); + + final float xGrowth = mBitmap.getWidth() * (getScaleX() - 1); + final float yGrowth = mBitmap.getHeight() * (getScaleY() - 1); + touch.set( + mRegistrationX + (int)Math.round(xGrowth / 2), + mRegistrationY + (int)Math.round(yGrowth / 2)); } @Override protected void onDraw(Canvas canvas) { - @SuppressWarnings("all") // suppress dead code warning - final boolean debug = false; - if (debug) { - Paint p = new Paint(); - p.setStyle(Paint.Style.FILL); - p.setColor(0x66ffffff); - canvas.drawRect(0, 0, getWidth(), getHeight(), p); - } - mHasDrawn = true; boolean crossFade = mCrossFadeProgress > 0 && mCrossFadeBitmap != null; if (crossFade) { @@ -214,12 +221,12 @@ public class DragView extends View { canvas.drawBitmap(mBitmap, 0.0f, 0.0f, mPaint); if (crossFade) { mPaint.setAlpha((int) (255 * mCrossFadeProgress)); - canvas.save(); + final int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG); float sX = (mBitmap.getWidth() * 1.0f) / mCrossFadeBitmap.getWidth(); float sY = (mBitmap.getHeight() * 1.0f) / mCrossFadeBitmap.getHeight(); canvas.scale(sX, sY); canvas.drawBitmap(mCrossFadeBitmap, 0.0f, 0.0f, mPaint); - canvas.restore(); + canvas.restoreToCount(saveCount); } } @@ -235,6 +242,7 @@ public class DragView extends View { @Override public void onAnimationUpdate(ValueAnimator animation) { mCrossFadeProgress = animation.getAnimatedFraction(); + invalidate(); } }); va.start(); @@ -328,16 +336,12 @@ public class DragView extends View { } public void cancelAnimation() { + mAnimationCancelled = true; if (mAnim != null && mAnim.isRunning()) { mAnim.cancel(); } } - public void resetLayoutParams() { - mOffsetX = mOffsetY = 0; - requestLayout(); - } - /** * Move the window containing this view. * @@ -345,8 +349,8 @@ public class DragView extends View { * @param touchY the y coordinate the user touched in DragLayer coordinates */ void move(int touchX, int touchY) { - setTranslationX(touchX - mRegistrationX + (int) mOffsetX); - setTranslationY(touchY - mRegistrationY + (int) mOffsetY); + setTranslationX(touchX - mRegistrationX); + setTranslationY(touchY - mRegistrationY); } void remove() { diff --git a/src/com/android/launcher3/SpringLoadedDragController.java b/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java index 45edaef86..d7f41c947 100644 --- a/src/com/android/launcher3/SpringLoadedDragController.java +++ b/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java @@ -14,7 +14,13 @@ * limitations under the License. */ -package com.android.launcher3; +package com.android.launcher3.dragndrop; + +import com.android.launcher3.Alarm; +import com.android.launcher3.CellLayout; +import com.android.launcher3.Launcher; +import com.android.launcher3.OnAlarmListener; +import com.android.launcher3.Workspace; public class SpringLoadedDragController implements OnAlarmListener { // how long the user must hover over a mini-screen before it unshrinks diff --git a/src/com/android/launcher3/model/PackageItemInfo.java b/src/com/android/launcher3/model/PackageItemInfo.java index 30f228c68..ddc9cbfd9 100644 --- a/src/com/android/launcher3/model/PackageItemInfo.java +++ b/src/com/android/launcher3/model/PackageItemInfo.java @@ -20,8 +20,6 @@ import android.graphics.Bitmap; import com.android.launcher3.ItemInfo; -import java.util.Arrays; - /** * Represents a {@link Package} in the widget tray section. */ @@ -59,7 +57,6 @@ public class PackageItemInfo extends ItemInfo { return "PackageItemInfo(title=" + title + " id=" + this.id + " type=" + this.itemType + " container=" + this.container + " screen=" + screenId + " cellX=" + cellX + " cellY=" + cellY - + " spanX=" + spanX + " spanY=" + spanY + " dropPos=" + Arrays.toString(dropPos) - + " user=" + user + ")"; + + " spanX=" + spanX + " spanY=" + spanY + " user=" + user + ")"; } } diff --git a/src/com/android/launcher3/DummyWidget.java b/src/com/android/launcher3/testing/DummyWidget.java index 59cd80501..df887ac1f 100644 --- a/src/com/android/launcher3/DummyWidget.java +++ b/src/com/android/launcher3/testing/DummyWidget.java @@ -1,7 +1,10 @@ -package com.android.launcher3; +package com.android.launcher3.testing; import android.appwidget.AppWidgetProviderInfo; +import com.android.launcher3.CustomAppWidget; +import com.android.launcher3.R; + public class DummyWidget implements CustomAppWidget { @Override public String getLabel() { @@ -20,7 +23,7 @@ public class DummyWidget implements CustomAppWidget { @Override public int getWidgetLayout() { - return R.layout.dummy_widget; + return R.layout.zzz_dummy_widget; } @Override diff --git a/src/com/android/launcher3/testing/LauncherExtension.java b/src/com/android/launcher3/testing/LauncherExtension.java index dac279afa..c6d5ad18b 100644 --- a/src/com/android/launcher3/testing/LauncherExtension.java +++ b/src/com/android/launcher3/testing/LauncherExtension.java @@ -363,7 +363,8 @@ public class LauncherExtension extends Launcher { @Override public void onScrollInteractionEnd() { if (mProgress > 25 && mLauncherOverlayCallbacks.enterFullImmersion()) { - ObjectAnimator oa = LauncherAnimUtils.ofFloat(mSearchOverlay, "translationX", 0); + ObjectAnimator oa = LauncherAnimUtils.ofFloat( + mSearchOverlay, View.TRANSLATION_X, 0); oa.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator arg0) { diff --git a/src/com/android/launcher3/MemoryDumpActivity.java b/src/com/android/launcher3/testing/MemoryDumpActivity.java index d79be8007..9bcf92b1b 100644 --- a/src/com/android/launcher3/MemoryDumpActivity.java +++ b/src/com/android/launcher3/testing/MemoryDumpActivity.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.launcher3; +package com.android.launcher3.testing; import android.app.Activity; import android.content.ComponentName; @@ -23,10 +23,20 @@ import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.net.Uri; -import android.os.*; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.os.IBinder; import android.util.Log; -import java.io.*; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.zip.ZipEntry; diff --git a/src/com/android/launcher3/MemoryTracker.java b/src/com/android/launcher3/testing/MemoryTracker.java index 067a50f97..ed2a3122c 100644 --- a/src/com/android/launcher3/MemoryTracker.java +++ b/src/com/android/launcher3/testing/MemoryTracker.java @@ -14,22 +14,28 @@ * limitations under the License. */ -package com.android.launcher3; +package com.android.launcher3.testing; import android.app.ActivityManager; import android.app.Service; import android.content.Context; import android.content.Intent; -import android.os.*; +import android.os.Binder; +import android.os.Debug; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.SystemClock; import android.util.Log; import android.util.LongSparseArray; +import com.android.launcher3.util.TestingUtils; + import java.util.ArrayList; import java.util.List; public class MemoryTracker extends Service { public static final String TAG = MemoryTracker.class.getSimpleName(); - public static final String ACTION_START_TRACKING = "com.android.launcher3.action.START_TRACKING"; private static final long UPDATE_RATE = 5000; @@ -83,14 +89,6 @@ public class MemoryTracker extends Service { ActivityManager mAm; - public static void startTrackingMe(Context context, String name) { - context.startService(new Intent(context, MemoryTracker.class) - .setAction(MemoryTracker.ACTION_START_TRACKING) - .putExtra("pid", android.os.Process.myPid()) - .putExtra("name", name) - ); - } - public ProcessMemInfo getMemInfo(int pid) { return mData.get(pid); } @@ -190,7 +188,7 @@ public class MemoryTracker extends Service { Log.v(TAG, "Received start id " + startId + ": " + intent); if (intent != null) { - if (ACTION_START_TRACKING.equals(intent.getAction())) { + if (TestingUtils.ACTION_START_TRACKING.equals(intent.getAction())) { final int pid = intent.getIntExtra("pid", -1); final String name = intent.getStringExtra("name"); final long start = intent.getLongExtra("start", System.currentTimeMillis()); diff --git a/src/com/android/launcher3/testing/ToggleWeightWatcher.java b/src/com/android/launcher3/testing/ToggleWeightWatcher.java new file mode 100644 index 000000000..15e55eea6 --- /dev/null +++ b/src/com/android/launcher3/testing/ToggleWeightWatcher.java @@ -0,0 +1,32 @@ +package com.android.launcher3.testing; + +import android.app.Activity; +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.view.View; + +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.util.TestingUtils; + +public class ToggleWeightWatcher extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + String spKey = LauncherAppState.getSharedPreferencesKey(); + SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_PRIVATE); + boolean show = sp.getBoolean(TestingUtils.SHOW_WEIGHT_WATCHER, true); + + show = !show; + sp.edit().putBoolean(TestingUtils.SHOW_WEIGHT_WATCHER, show).apply(); + + Launcher launcher = (Launcher) LauncherAppState.getInstance().getModel().getCallback(); + if (launcher != null && launcher.mWeightWatcher != null) { + launcher.mWeightWatcher.setVisibility(show ? View.VISIBLE : View.GONE); + } + finish(); + } +} diff --git a/src/com/android/launcher3/WeightWatcher.java b/src/com/android/launcher3/testing/WeightWatcher.java index 75684797f..a26a2b642 100644 --- a/src/com/android/launcher3/WeightWatcher.java +++ b/src/com/android/launcher3/testing/WeightWatcher.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.launcher3; +package com.android.launcher3.testing; import android.content.ComponentName; import android.content.Context; @@ -116,10 +116,6 @@ public class WeightWatcher extends LinearLayout { } } - public WeightWatcher(Context context) { - this(context, null); - } - @Override public void onAttachedToWindow() { super.onAttachedToWindow(); diff --git a/src/com/android/launcher3/util/FlingAnimation.java b/src/com/android/launcher3/util/FlingAnimation.java index 55c5d7dc2..da8bae712 100644 --- a/src/com/android/launcher3/util/FlingAnimation.java +++ b/src/com/android/launcher3/util/FlingAnimation.java @@ -7,9 +7,9 @@ import android.graphics.PointF; import android.graphics.Rect; import android.view.animation.DecelerateInterpolator; -import com.android.launcher3.DragLayer; -import com.android.launcher3.DragView; import com.android.launcher3.DropTarget.DragObject; +import com.android.launcher3.dragndrop.DragLayer; +import com.android.launcher3.dragndrop.DragView; public class FlingAnimation implements AnimatorUpdateListener { @@ -51,7 +51,7 @@ public class FlingAnimation implements AnimatorUpdateListener { mFrom.top += yOffset; mFrom.bottom -= yOffset; - mDuration = initDuration(); + mDuration = Math.abs(vel.y) > Math.abs(vel.x) ? initFlingUpDuration() : initFlingLeftDuration(); mAnimationTimeFraction = ((float) mDuration) / (mDuration + DRAG_END_DELAY); } @@ -62,7 +62,7 @@ public class FlingAnimation implements AnimatorUpdateListener { * - Calculate a constant acceleration in x direction such that the object reaches * {@link #mIconRect} in the given time. */ - protected int initDuration() { + protected int initFlingUpDuration() { float sY = -mFrom.bottom; float d = mUY * mUY + 2 * sY * MAX_ACCELERATION; @@ -83,6 +83,34 @@ public class FlingAnimation implements AnimatorUpdateListener { return (int) Math.round(t); } + /** + * The fling animation is based on the following system + * - Apply a constant force in the x direction to causing the fling to decelerate. + * - The animation runs for the time taken by the object to go out of the screen. + * - Calculate a constant acceleration in y direction such that the object reaches + * {@link #mIconRect} in the given time. + */ + protected int initFlingLeftDuration() { + float sX = -mFrom.right; + + float d = mUX * mUX + 2 * sX * MAX_ACCELERATION; + if (d >= 0) { + // sX can be reached under the MAX_ACCELERATION. Use MAX_ACCELERATION for x direction. + mAX = MAX_ACCELERATION; + } else { + // sX is not reachable, decrease the acceleration so that sX is almost reached. + d = 0; + mAX = mUX * mUX / (2 * -sX); + } + double t = (-mUX - Math.sqrt(d)) / mAX; + + float sY = -mFrom.exactCenterY() + mIconRect.exactCenterY(); + + // Find vertical acceleration such that: u*t + a*t*t/2 = s + mAY = (float) ((sY - t * mUY) * 2 / (t * t)); + return (int) Math.round(t); + } + public final int getDuration() { return mDuration + DRAG_END_DELAY; } diff --git a/src/com/android/launcher3/util/FocusLogic.java b/src/com/android/launcher3/util/FocusLogic.java index 696eabe00..1c6efbcf6 100644 --- a/src/com/android/launcher3/util/FocusLogic.java +++ b/src/com/android/launcher3/util/FocusLogic.java @@ -22,9 +22,6 @@ import android.view.View; import android.view.ViewGroup; import com.android.launcher3.CellLayout; -import com.android.launcher3.DeviceProfile; -import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherAppState; import com.android.launcher3.ShortcutAndWidgetContainer; import java.util.Arrays; diff --git a/src/com/android/launcher3/util/ParcelableSparseArray.java b/src/com/android/launcher3/util/ParcelableSparseArray.java new file mode 100644 index 000000000..093577e59 --- /dev/null +++ b/src/com/android/launcher3/util/ParcelableSparseArray.java @@ -0,0 +1,52 @@ +/** + * 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.launcher3.util; + +import android.os.Parcel; +import android.os.Parcelable; +import android.util.SparseArray; + +public class ParcelableSparseArray extends SparseArray<Parcelable> implements Parcelable { + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel dest, int flags) { + final int count = size(); + dest.writeInt(count); + for (int i = 0; i < count; i++) { + dest.writeInt(keyAt(i)); + dest.writeParcelable(valueAt(i), 0); + } + } + + public static final Parcelable.Creator<ParcelableSparseArray> CREATOR = + new Parcelable.Creator<ParcelableSparseArray>() { + public ParcelableSparseArray createFromParcel(Parcel source) { + final ParcelableSparseArray array = new ParcelableSparseArray(); + final ClassLoader loader = array.getClass().getClassLoader(); + final int count = source.readInt(); + for (int i = 0; i < count; i++) { + array.put(source.readInt(), source.readParcelable(loader)); + } + return array; + } + + public ParcelableSparseArray[] newArray(int size) { + return new ParcelableSparseArray[size]; + } + }; +} diff --git a/src/com/android/launcher3/util/TestingUtils.java b/src/com/android/launcher3/util/TestingUtils.java new file mode 100644 index 000000000..39b80463d --- /dev/null +++ b/src/com/android/launcher3/util/TestingUtils.java @@ -0,0 +1,73 @@ +package com.android.launcher3.util; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.util.Log; +import android.view.Gravity; +import android.view.View; +import android.widget.FrameLayout; + +import com.android.launcher3.CustomAppWidget; +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.R; + +import java.util.HashMap; + +public class TestingUtils { + + public static final String MEMORY_TRACKER = "com.android.launcher3.testing.MemoryTracker"; + public static final String ACTION_START_TRACKING = "com.android.launcher3.action.START_TRACKING"; + + public static final boolean MEMORY_DUMP_ENABLED = false; + public static final String SHOW_WEIGHT_WATCHER = "debug.show_mem"; + + public static final boolean ENABLE_CUSTOM_WIDGET_TEST = false; + public static final String DUMMY_WIDGET = "com.android.launcher3.testing.DummyWidget"; + + public static void startTrackingMemory(Context context) { + if (MEMORY_DUMP_ENABLED) { + context.startService(new Intent() + .setComponent(new ComponentName(context.getPackageName(), MEMORY_TRACKER)) + .setAction(ACTION_START_TRACKING) + .putExtra("pid", android.os.Process.myPid()) + .putExtra("name", "L")); + } + } + + public static void addWeightWatcher(Launcher launcher) { + if (MEMORY_DUMP_ENABLED) { + String spKey = LauncherAppState.getSharedPreferencesKey(); + SharedPreferences sp = launcher.getSharedPreferences(spKey, Context.MODE_PRIVATE); + boolean show = sp.getBoolean(SHOW_WEIGHT_WATCHER, true); + + int id = launcher.getResources().getIdentifier("zzz_weight_watcher", "layout", + launcher.getPackageName()); + View watcher = launcher.getLayoutInflater().inflate(id, null); + watcher.setAlpha(0.5f); + ((FrameLayout) launcher.findViewById(R.id.launcher)).addView(watcher, + new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.WRAP_CONTENT, + Gravity.BOTTOM) + ); + + watcher.setVisibility(show ? View.VISIBLE : View.GONE); + launcher.mWeightWatcher = watcher; + } + } + + public static void addDummyWidget(HashMap<String, CustomAppWidget> set) { + if (ENABLE_CUSTOM_WIDGET_TEST) { + try { + Class<?> clazz = Class.forName(DUMMY_WIDGET); + CustomAppWidget widget = (CustomAppWidget) clazz.newInstance(); + set.put(widget.getClass().getName(), widget); + } catch (Exception e) { + Log.e("TestingUtils", "Error adding dummy widget", e); + } + } + } +} diff --git a/src/com/android/launcher3/util/ViewOnDrawExecutor.java b/src/com/android/launcher3/util/ViewOnDrawExecutor.java new file mode 100644 index 000000000..01808ba57 --- /dev/null +++ b/src/com/android/launcher3/util/ViewOnDrawExecutor.java @@ -0,0 +1,96 @@ +/** + * 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.launcher3.util; + +import android.view.View; +import android.view.View.OnAttachStateChangeListener; +import android.view.ViewTreeObserver.OnDrawListener; + +import com.android.launcher3.DeferredHandler; +import com.android.launcher3.Launcher; + +import java.util.ArrayList; +import java.util.concurrent.Executor; + +/** + * An executor which runs all the tasks after the first onDraw is called on the target view. + */ +public class ViewOnDrawExecutor implements Executor, OnDrawListener, Runnable, + OnAttachStateChangeListener { + + private final ArrayList<Runnable> mTasks = new ArrayList<>(); + private final DeferredHandler mHandler; + + private Launcher mLauncher; + private View mAttachedView; + private boolean mCompleted; + + public ViewOnDrawExecutor(DeferredHandler handler) { + mHandler = handler; + } + + public void attachTo(Launcher launcher) { + mLauncher = launcher; + mAttachedView = launcher.getWorkspace(); + mAttachedView.addOnAttachStateChangeListener(this); + + attachObserver(); + } + + private void attachObserver() { + if (!mCompleted) { + mAttachedView.getViewTreeObserver().addOnDrawListener(this); + } + } + + @Override + public void execute(Runnable command) { + mTasks.add(command); + } + + @Override + public void onViewAttachedToWindow(View v) { + attachObserver(); + } + + @Override + public void onViewDetachedFromWindow(View v) { } + + @Override + public void onDraw() { + mAttachedView.post(this); + } + + @Override + public void run() { + for (final Runnable r : mTasks) { + mHandler.post(r); + } + markCompleted(); + } + + public void markCompleted() { + mTasks.clear(); + if (mAttachedView != null) { + mAttachedView.getViewTreeObserver().removeOnDrawListener(this); + mAttachedView.removeOnAttachStateChangeListener(this); + } + if (mLauncher != null) { + mLauncher.clearPendingExecutor(this); + } + } +} diff --git a/src/com/android/launcher3/util/WallpaperUtils.java b/src/com/android/launcher3/util/WallpaperUtils.java index b9fccbcfd..a5251e190 100644 --- a/src/com/android/launcher3/util/WallpaperUtils.java +++ b/src/com/android/launcher3/util/WallpaperUtils.java @@ -17,13 +17,16 @@ package com.android.launcher3.util; import android.annotation.TargetApi; +import android.app.Activity; import android.app.WallpaperManager; +import android.content.Context; import android.content.SharedPreferences; import android.content.res.Resources; import android.graphics.Point; import android.os.Build; import android.view.WindowManager; +import com.android.launcher3.LauncherFiles; import com.android.launcher3.Utilities; /** @@ -35,28 +38,59 @@ public final class WallpaperUtils { public static final String WALLPAPER_HEIGHT_KEY = "wallpaper.height"; public static final float WALLPAPER_SCREENS_SPAN = 2f; - public static void suggestWallpaperDimension(Resources res, - final SharedPreferences sharedPrefs, - WindowManager windowManager, - final WallpaperManager wallpaperManager, boolean fallBackToDefaults) { - final Point defaultWallpaperSize = WallpaperUtils.getDefaultWallpaperSize(res, windowManager); - // If we have saved a wallpaper width/height, use that instead + public static void saveWallpaperDimensions(int width, int height, Activity activity) { + if (Utilities.ATLEAST_KITKAT) { + // From Kitkat onwards, ImageWallpaper does not care about the + // desired width and desired height of the wallpaper. + return; + } + String spKey = LauncherFiles.WALLPAPER_CROP_PREFERENCES_KEY; + SharedPreferences sp = activity.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); + } + editor.commit(); + suggestWallpaperDimensionPreK(activity, true); + } + + public static void suggestWallpaperDimensionPreK( + Activity activity, boolean fallBackToDefaults) { + final Point defaultWallpaperSize = getDefaultWallpaperSize( + activity.getResources(), activity.getWindowManager()); - int savedWidth = sharedPrefs.getInt(WALLPAPER_WIDTH_KEY, -1); - int savedHeight = sharedPrefs.getInt(WALLPAPER_HEIGHT_KEY, -1); + SharedPreferences sp = activity.getSharedPreferences( + LauncherFiles.WALLPAPER_CROP_PREFERENCES_KEY, Context.MODE_MULTI_PROCESS); + // If we have saved a wallpaper width/height, use that instead + int width = sp.getInt(WALLPAPER_WIDTH_KEY, -1); + int height = sp.getInt(WALLPAPER_HEIGHT_KEY, -1); - if (savedWidth == -1 || savedHeight == -1) { + if (width == -1 || height == -1) { if (!fallBackToDefaults) { return; } else { - savedWidth = defaultWallpaperSize.x; - savedHeight = defaultWallpaperSize.y; + width = defaultWallpaperSize.x; + height = defaultWallpaperSize.y; } } - if (savedWidth != wallpaperManager.getDesiredMinimumWidth() || - savedHeight != wallpaperManager.getDesiredMinimumHeight()) { - wallpaperManager.suggestDesiredDimensions(savedWidth, savedHeight); + WallpaperManager wm = WallpaperManager.getInstance(activity); + if (width != wm.getDesiredMinimumWidth() || height != wm.getDesiredMinimumHeight()) { + wm.suggestDesiredDimensions(width, height); + } + } + + public static void suggestWallpaperDimension(Activity activity) { + // Only live wallpapers care about desired size. Update the size to what launcher expects. + final Point size = getDefaultWallpaperSize( + activity.getResources(), activity.getWindowManager()); + WallpaperManager wm = WallpaperManager.getInstance(activity); + if (size.x != wm.getDesiredMinimumWidth() || size.y != wm.getDesiredMinimumHeight()) { + wm.suggestDesiredDimensions(size.x, size.y); } } @@ -64,7 +98,7 @@ public final class WallpaperUtils { * As a ratio of screen height, the total distance we want the parallax effect to span * horizontally */ - public static float wallpaperTravelToScreenWidthRatio(int width, int height) { + private static float wallpaperTravelToScreenWidthRatio(int width, int height) { float aspectRatio = width / (float) height; // At an aspect ratio of 16/10, the wallpaper parallax effect should span 1.5 * screen width @@ -94,19 +128,10 @@ public final class WallpaperUtils { @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) public static Point getDefaultWallpaperSize(Resources res, WindowManager windowManager) { if (sDefaultWallpaperSize == null) { - Point minDims = new Point(); - Point maxDims = new Point(); - windowManager.getDefaultDisplay().getCurrentSizeRange(minDims, maxDims); - - int maxDim = Math.max(maxDims.x, maxDims.y); - int minDim = Math.max(minDims.x, minDims.y); - - if (Utilities.ATLEAST_JB_MR1) { - Point realSize = new Point(); - windowManager.getDefaultDisplay().getRealSize(realSize); - maxDim = Math.max(realSize.x, realSize.y); - minDim = Math.min(realSize.x, realSize.y); - } + Point realSize = new Point(); + windowManager.getDefaultDisplay().getRealSize(realSize); + int maxDim = Math.max(realSize.x, realSize.y); + int minDim = Math.min(realSize.x, realSize.y); // We need to ensure that there is enough extra space in the wallpaper // for the intended parallax effects diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java index 94bbd929f..70eceb9ce 100644 --- a/src/com/android/launcher3/widget/WidgetCell.java +++ b/src/com/android/launcher3/widget/WidgetCell.java @@ -36,6 +36,7 @@ import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherAppWidgetProviderInfo; +import com.android.launcher3.SimpleOnStylusPressListener; import com.android.launcher3.R; import com.android.launcher3.StylusEventHelper; import com.android.launcher3.WidgetPreviewLoader; @@ -93,7 +94,7 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { final Resources r = context.getResources(); mLauncher = (Launcher) context; - mStylusEventHelper = new StylusEventHelper(this); + mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this); mDimensionsFormatString = r.getString(R.string.widget_dims_format); setContainerWidth(); @@ -211,7 +212,7 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { @Override public boolean onTouchEvent(MotionEvent ev) { boolean handled = super.onTouchEvent(ev); - if (mStylusEventHelper.checkAndPerformStylusEvent(ev)) { + if (mStylusEventHelper.onMotionEvent(ev)) { return true; } return handled; diff --git a/src/com/android/launcher3/widget/WidgetHostViewLoader.java b/src/com/android/launcher3/widget/WidgetHostViewLoader.java index 461aebb6b..7607d8554 100644 --- a/src/com/android/launcher3/widget/WidgetHostViewLoader.java +++ b/src/com/android/launcher3/widget/WidgetHostViewLoader.java @@ -10,13 +10,14 @@ import android.os.Handler; import android.view.View; import com.android.launcher3.AppWidgetResizeFrame; -import com.android.launcher3.DragController.DragListener; -import com.android.launcher3.DragLayer; import com.android.launcher3.DragSource; +import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppWidgetProviderInfo; import com.android.launcher3.Utilities; import com.android.launcher3.compat.AppWidgetManagerCompat; +import com.android.launcher3.dragndrop.DragController.DragListener; +import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.util.Thunk; public class WidgetHostViewLoader implements DragListener { @@ -44,7 +45,7 @@ public class WidgetHostViewLoader implements DragListener { } @Override - public void onDragStart(DragSource source, Object info, int dragAction) { } + public void onDragStart(DragSource source, ItemInfo info, int dragAction) { } @Override public void onDragEnd() { diff --git a/src/com/android/launcher3/widget/WidgetsContainerView.java b/src/com/android/launcher3/widget/WidgetsContainerView.java index 0c6ea31bb..81a8465d8 100644 --- a/src/com/android/launcher3/widget/WidgetsContainerView.java +++ b/src/com/android/launcher3/widget/WidgetsContainerView.java @@ -32,7 +32,6 @@ import com.android.launcher3.BaseContainerView; import com.android.launcher3.CellLayout; import com.android.launcher3.DeleteDropTarget; import com.android.launcher3.DeviceProfile; -import com.android.launcher3.DragController; import com.android.launcher3.DragSource; import com.android.launcher3.DropTarget.DragObject; import com.android.launcher3.Folder; @@ -45,6 +44,7 @@ import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.WidgetPreviewLoader; import com.android.launcher3.Workspace; +import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.model.WidgetsModel; import com.android.launcher3.util.Thunk; @@ -90,7 +90,7 @@ public class WidgetsContainerView extends BaseContainerView super(context, attrs, defStyleAttr); mLauncher = (Launcher) context; mDragController = mLauncher.getDragController(); - mAdapter = new WidgetsListAdapter(context, this, this, mLauncher); + mAdapter = new WidgetsListAdapter(this, this, mLauncher); mIconCache = (LauncherAppState.getInstance()).getIconCache(); if (DEBUG) { Log.d(TAG, "WidgetsContainerView constructor"); @@ -253,9 +253,11 @@ public class WidgetsContainerView extends BaseContainerView // Start the drag mLauncher.lockScreenOrientation(); - mLauncher.getWorkspace().onDragStartedWithItem(createItemInfo, preview, clipAlpha); mDragController.startDrag(image, preview, this, createItemInfo, bounds, DragController.DRAG_ACTION_COPY, scale); + // This call expects the extra empty screen to already be created, which is why we call it + // after mDragController.startDrag(). + mLauncher.getWorkspace().onDragStartedWithItem(createItemInfo, preview, clipAlpha); preview.recycle(); return true; @@ -317,7 +319,7 @@ public class WidgetsContainerView extends BaseContainerView int currentScreen = mLauncher.getCurrentWorkspaceScreen(); Workspace workspace = (Workspace) target; CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen); - ItemInfo itemInfo = (ItemInfo) d.dragInfo; + ItemInfo itemInfo = d.dragInfo; if (layout != null) { showOutOfSpaceMessage = !layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY); diff --git a/src/com/android/launcher3/widget/WidgetsListAdapter.java b/src/com/android/launcher3/widget/WidgetsListAdapter.java index f1cde299d..ac9d62e9a 100644 --- a/src/com/android/launcher3/widget/WidgetsListAdapter.java +++ b/src/com/android/launcher3/widget/WidgetsListAdapter.java @@ -27,7 +27,6 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; -import android.view.ViewGroup.MarginLayoutParams; import android.widget.LinearLayout; import com.android.launcher3.BubbleTextView; @@ -65,20 +64,17 @@ public class WidgetsListAdapter extends Adapter<WidgetsRowViewHolder> { private View.OnClickListener mIconClickListener; private View.OnLongClickListener mIconLongClickListener; - private static final int PRESET_INDENT_SIZE_TABLET = 56; - private int mIndent = 0; + private final int mIndent; - public WidgetsListAdapter(Context context, - View.OnClickListener iconClickListener, + public WidgetsListAdapter(View.OnClickListener iconClickListener, View.OnLongClickListener iconLongClickListener, Launcher launcher) { - mLayoutInflater = LayoutInflater.from(context); + mLayoutInflater = launcher.getLayoutInflater(); mIconClickListener = iconClickListener; mIconLongClickListener = iconLongClickListener; mLauncher = launcher; - - setContainerHeight(); + mIndent = launcher.getResources().getDimensionPixelSize(R.dimen.widget_section_indent); } public void setWidgetsModel(WidgetsModel w) { @@ -207,12 +203,4 @@ public class WidgetsListAdapter extends Adapter<WidgetsRowViewHolder> { } return mWidgetPreviewLoader; } - - private void setContainerHeight() { - Resources r = mLauncher.getResources(); - DeviceProfile profile = mLauncher.getDeviceProfile(); - if (profile.isLargeTablet || profile.isTablet) { - mIndent = Utilities.pxFromDp(PRESET_INDENT_SIZE_TABLET, r.getDisplayMetrics()); - } - } } diff --git a/src/com/android/launcher3/widget/WidgetsRowViewHolder.java b/src/com/android/launcher3/widget/WidgetsRowViewHolder.java index 249559ab9..19bc868a4 100644 --- a/src/com/android/launcher3/widget/WidgetsRowViewHolder.java +++ b/src/com/android/launcher3/widget/WidgetsRowViewHolder.java @@ -16,10 +16,7 @@ package com.android.launcher3.widget; import android.support.v7.widget.RecyclerView.ViewHolder; -import android.view.View; import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; public class WidgetsRowViewHolder extends ViewHolder { |