Merge "Revert "Specifies descendantFocusability to Folder view."" into ub-launcher3-master
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 1fb8e8d..ef210d4 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -51,11 +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.GET_ACCOUNTS" />
<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" />
@@ -181,13 +178,6 @@
>
</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"
@@ -204,12 +194,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"
diff --git a/WallpaperPicker/src/com/android/gallery3d/common/BitmapUtils.java b/WallpaperPicker/src/com/android/gallery3d/common/BitmapUtils.java
index 9ac5c1b..6ddda87 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.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 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 0000000..ad4370c
--- /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 7fb9f22..0000000
--- 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 dfd4a1a..0000000
--- 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 8422382..0000000
--- 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 9247e87..0000000
--- 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 bf923ec..0000000
--- 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 0531cba..0000000
--- 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 7ca05f2..0000000
--- 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 5467d42..0000000
--- 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 68e972f..0000000
--- 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 b8b3872..0000000
--- 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 093944a..0000000
--- 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 7842edb..0000000
--- 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 e3e787e..0000000
--- 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 428e6b9..0000000
--- 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 591d63f..0000000
--- 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 0f3efb7..7270e88 100644
--- a/WallpaperPicker/src/com/android/gallery3d/glrenderer/BasicTexture.java
+++ b/WallpaperPicker/src/com/android/gallery3d/glrenderer/BasicTexture.java
@@ -46,8 +46,6 @@
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 boolean isFlippedVertically() {
- return false;
- }
-
public int getId() {
return mId;
}
@@ -113,25 +107,6 @@
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 @@
// 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 @@
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 f8b01cb..bb69b68 100644
--- a/WallpaperPicker/src/com/android/gallery3d/glrenderer/BitmapTexture.java
+++ b/WallpaperPicker/src/com/android/gallery3d/glrenderer/BitmapTexture.java
@@ -29,11 +29,7 @@
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 5b07477..2bda8d2 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 @@
// 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 @@
// 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 @@
* @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 933260b..0da3bae 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.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 @@
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 @@
+ " 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 @@
+ " 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 @@
// 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 @@
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 @@
}
}
- 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 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 @@
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 @@
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 @@
}
@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 @@
}
@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 @@
@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 @@
}
@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 @@
}
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 @@
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 @@
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,89 +420,20 @@
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());
+ GLES20.glUniform1f(params[INDEX_ALPHA].handle, 1);
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);
- 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
public boolean unloadTexture(BasicTexture texture) {
boolean unload = texture.isLoaded();
if (unload) {
@@ -793,13 +445,6 @@
}
@Override
- public void deleteBuffer(int bufferId) {
- synchronized (mUnboundTextures) {
- mDeleteBuffers.add(bufferId);
- }
- }
-
- @Override
public void deleteRecycledResources() {
synchronized (mUnboundTextures) {
IntArray ids = mUnboundTextures;
@@ -807,134 +452,41 @@
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 @@
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 @@
}
}
- @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 b26e9ab..0000000
--- 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 93f0fdf..0000000
--- 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 3dcae4a..e71a379 100644
--- a/WallpaperPicker/src/com/android/gallery3d/glrenderer/Texture.java
+++ b/WallpaperPicker/src/com/android/gallery3d/glrenderer/Texture.java
@@ -24,21 +24,14 @@
// 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 8075bf8..607e2a9 100644
--- a/WallpaperPicker/src/com/android/gallery3d/glrenderer/UploadedTexture.java
+++ b/WallpaperPicker/src/com/android/gallery3d/glrenderer/UploadedTexture.java
@@ -19,14 +19,12 @@
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 @@
// 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();
+ private static HashMap<BorderKey, Bitmap> sBorderLines = new HashMap<BorderKey, Bitmap>();
- @SuppressWarnings("unused")
- private static final String TAG = "Texture";
+ 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;
-
- // 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;
-
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 @@
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 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 @@
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 @@
}
@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/WallpaperCropActivity.java b/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java
index f2bb509..eb47380 100644
--- a/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java
+++ b/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java
@@ -19,10 +19,7 @@
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.res.Resources;
import android.graphics.Bitmap;
@@ -58,9 +55,6 @@
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
@@ -69,7 +63,6 @@
* 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;
@@ -223,14 +216,12 @@
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
protected boolean isActivityDestroyed() {
- return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1)
- && isDestroyed();
+ return Utilities.ATLEAST_JB_MR1 && isDestroyed();
}
@Thunk void addReusableBitmap(TileSource src) {
synchronized (mReusableBitmaps) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
- && src instanceof BitmapRegionTileSource) {
+ if (Utilities.ATLEAST_KITKAT && src instanceof BitmapRegionTileSource) {
Bitmap preview = ((BitmapRegionTileSource) src).getBitmap();
if (preview != null && preview.isMutable()) {
mReusableBitmaps.add(preview);
@@ -304,7 +295,7 @@
final Point bounds = cropTask.getImageBounds();
Runnable onEndCrop = new Runnable() {
public void run() {
- updateWallpaperDimensions(bounds.x, bounds.y);
+ WallpaperUtils.saveWallpaperDimensions(bounds.x, bounds.y, WallpaperCropActivity.this);
if (finishActivityWhenDone) {
setResult(Activity.RESULT_OK);
finish();
@@ -320,7 +311,7 @@
Resources res, int resId, final boolean finishActivityWhenDone) {
// 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());
@@ -330,7 +321,7 @@
public void run() {
// Passing 0, 0 will cause launcher to revert to using the
// default wallpaper size
- updateWallpaperDimensions(0, 0);
+ WallpaperUtils.saveWallpaperDimensions(0, 0, WallpaperCropActivity.this);
if (finishActivityWhenDone) {
setResult(Activity.RESULT_OK);
finish();
@@ -422,7 +413,7 @@
Runnable onEndCrop = new Runnable() {
public void run() {
- updateWallpaperDimensions(outWidth, outHeight);
+ WallpaperUtils.saveWallpaperDimensions(outWidth, outHeight, WallpaperCropActivity.this);
if (finishActivityWhenDone) {
setResult(Activity.RESULT_OK);
finish();
@@ -437,22 +428,6 @@
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);
- }
- editor.commit();
- WallpaperUtils.suggestWallpaperDimension(getResources(),
- sp, getWindowManager(), WallpaperManager.getInstance(getContext()), true);
- }
-
static class LoadRequest {
BitmapSource src;
boolean touchEnabled;
diff --git a/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java b/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java
index 88dc3e2..e2c008b 100644
--- a/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java
+++ b/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java
@@ -16,7 +16,7 @@
package com.android.launcher3;
-import android.Manifest.permission;
+import android.Manifest;
import android.animation.LayoutTransition;
import android.annotation.TargetApi;
import android.app.ActionBar;
@@ -38,7 +38,6 @@
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
-import android.Manifest;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
@@ -100,6 +99,7 @@
@Thunk LinearLayout mWallpapersView;
@Thunk HorizontalScrollView mWallpaperScrollContainer;
+ @Thunk View mWallpaperStrip;
@Thunk ActionMode.Callback mActionModeCallback;
@Thunk ActionMode mActionMode;
@@ -200,8 +200,8 @@
@Override
public void onClick(final WallpaperPickerActivity a) {
a.setWallpaperButtonEnabled(false);
- final BitmapRegionTileSource.UriBitmapSource bitmapSource =
- new BitmapRegionTileSource.UriBitmapSource(a.getContext(), Uri.fromFile(mFile));
+ final BitmapRegionTileSource.FilePathBitmapSource bitmapSource =
+ new BitmapRegionTileSource.FilePathBitmapSource(mFile.getAbsolutePath());
a.setCropViewTileSource(bitmapSource, false, true, null, new Runnable() {
@Override
@@ -239,7 +239,7 @@
public void onClick(final WallpaperPickerActivity a) {
a.setWallpaperButtonEnabled(false);
final BitmapRegionTileSource.ResourceBitmapSource bitmapSource =
- new BitmapRegionTileSource.ResourceBitmapSource(mResources, mResId);
+ new BitmapRegionTileSource.ResourceBitmapSource(mResources, mResId, a);
a.setCropViewTileSource(bitmapSource, false, false, new CropViewScaleProvider() {
@Override
@@ -379,6 +379,7 @@
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
@@ -386,15 +387,15 @@
if (mAnim != null) {
mAnim.cancel();
}
- if (mWallpaperScrollContainer.getAlpha() == 1f) {
+ if (mWallpaperStrip.getAlpha() == 1f) {
mIgnoreNextTap = true;
}
- mAnim = mWallpaperScrollContainer.animate();
+ mAnim = mWallpaperStrip.animate();
mAnim.alpha(0f)
.setDuration(150)
.withEndAction(new Runnable() {
public void run() {
- mWallpaperScrollContainer.setVisibility(View.INVISIBLE);
+ mWallpaperStrip.setVisibility(View.INVISIBLE);
}
});
mAnim.setInterpolator(new AccelerateInterpolator(0.75f));
@@ -412,8 +413,8 @@
if (mAnim != null) {
mAnim.cancel();
}
- mWallpaperScrollContainer.setVisibility(View.VISIBLE);
- mAnim = mWallpaperScrollContainer.animate();
+ mWallpaperStrip.setVisibility(View.VISIBLE);
+ mAnim = mWallpaperStrip.animate();
mAnim.alpha(1f)
.setDuration(150)
.setInterpolator(new DecelerateInterpolator(0.75f));
@@ -548,7 +549,12 @@
new View.OnClickListener() {
@Override
public void onClick(View v) {
- if (mSelectedTile != null) {
+ // Ensure that a tile is slelected and loaded.
+ if (mSelectedTile != null && mCropView.getTileSource() != null) {
+ // Prevent user from selecting any new tile.
+ mWallpaperStrip.setVisibility(View.GONE);
+ actionBar.hide();
+
WallpaperTileInfo info = (WallpaperTileInfo) mSelectedTile.getTag();
info.onSave(WallpaperPickerActivity.this);
} else {
@@ -713,10 +719,10 @@
public void onStop() {
super.onStop();
- mWallpaperScrollContainer = (HorizontalScrollView) findViewById(R.id.wallpaper_scroll_container);
- if (mWallpaperScrollContainer.getAlpha() < 1f) {
- mWallpaperScrollContainer.setAlpha(1f);
- mWallpaperScrollContainer.setVisibility(View.VISIBLE);
+ mWallpaperStrip = findViewById(R.id.wallpaper_strip);
+ if (mWallpaperStrip.getAlpha() < 1f) {
+ mWallpaperStrip.setAlpha(1f);
+ mWallpaperStrip.setVisibility(View.VISIBLE);
}
}
@@ -970,10 +976,8 @@
if (partner == null || !partner.hideDefaultWallpaper()) {
// Add an entry for the default wallpaper (stored in system resources)
- WallpaperTileInfo defaultWallpaperInfo =
- (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT)
- ? getPreKKDefaultWallpaperInfo()
- : getDefaultWallpaper();
+ WallpaperTileInfo defaultWallpaperInfo = Utilities.ATLEAST_KITKAT
+ ? getDefaultWallpaper() : getPreKKDefaultWallpaperInfo();
if (defaultWallpaperInfo != null) {
bundled.add(0, defaultWallpaperInfo);
}
@@ -1026,7 +1030,7 @@
} else {
Resources res = getResources();
Point defaultThumbSize = getDefaultThumbnailSize(res);
- int rotation = BitmapUtils.getRotationFromExif(res, resId);
+ int rotation = BitmapUtils.getRotationFromExif(res, resId, this);
thumb = createThumbnail(
defaultThumbSize, getContext(), null, null, sysRes, resId, rotation, false);
if (thumb != null) {
diff --git a/WallpaperPicker/src/com/android/photos/BitmapRegionTileSource.java b/WallpaperPicker/src/com/android/photos/BitmapRegionTileSource.java
index 2d496a5..2f9c9a3 100644
--- a/WallpaperPicker/src/com/android/photos/BitmapRegionTileSource.java
+++ b/WallpaperPicker/src/com/android/photos/BitmapRegionTileSource.java
@@ -31,8 +31,8 @@
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 @@
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 @@
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 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 @@
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 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 7e3e1a9..6f7a530 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 @@
mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
addView(mGLSurfaceView, new LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
- //setTileSource(new ColoredTiles());
}
@Override
@@ -247,66 +241,4 @@
}
}
-
- @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 a8e2b60..5f393fc 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -3,10 +3,10 @@
}
-keep class com.android.launcher3.BaseRecyclerViewFastScrollBar {
- public void setWidth(int);
- public int getWidth();
- public void setTrackAlpha(int);
- public int getTrackAlpha();
+ public void setThumbWidth(int);
+ public int getThumbWidth();
+ public void setTrackWidth(int);
+ public int getTrackWidth();
}
-keep class com.android.launcher3.BaseRecyclerViewFastScrollPopup {
@@ -39,7 +39,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/protos/backup.proto b/protos/backup.proto
index d8d94e8..6704e08 100644
--- a/protos/backup.proto
+++ b/protos/backup.proto
@@ -101,7 +101,10 @@
optional string iconPackage = 16;
optional string iconResource = 17;
optional bytes icon = 18;
+
+ // Added in backup version 4
optional TargetType targetType = 19 [default = TARGET_NONE];
+ optional int32 rank = 20;
}
message Screen {
@@ -121,6 +124,7 @@
optional Resource icon = 4;
optional Resource preview = 5;
+ // Added in backup version 3
// Assume that a widget is resizable upto 2x2 if no data is available
optional int32 minSpanX = 6 [default = 2];
optional int32 minSpanY = 7 [default = 2];
diff --git a/res/drawable-hdpi/ic_arrow_back_grey.png b/res/drawable-hdpi/ic_arrow_back_grey.png
index ccd3900..c7c0088 100755
--- a/res/drawable-hdpi/ic_arrow_back_grey.png
+++ b/res/drawable-hdpi/ic_arrow_back_grey.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_search_grey.png b/res/drawable-hdpi/ic_search_grey.png
index f4c5e27..bd20ba0 100755
--- a/res/drawable-hdpi/ic_search_grey.png
+++ b/res/drawable-hdpi/ic_search_grey.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_arrow_back_grey.png b/res/drawable-mdpi/ic_arrow_back_grey.png
index 11996ef..5892c77 100755
--- a/res/drawable-mdpi/ic_arrow_back_grey.png
+++ b/res/drawable-mdpi/ic_arrow_back_grey.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_search_grey.png b/res/drawable-mdpi/ic_search_grey.png
index e83891c..c386dbb 100755
--- a/res/drawable-mdpi/ic_search_grey.png
+++ b/res/drawable-mdpi/ic_search_grey.png
Binary files differ
diff --git a/res/drawable-v21/all_apps_search_market_bg.xml b/res/drawable-v21/all_apps_search_market_bg.xml
new file mode 100644
index 0000000..7bd2f88
--- /dev/null
+++ b/res/drawable-v21/all_apps_search_market_bg.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="@color/all_apps_search_market_button_focused_bg_color">
+ <item android:drawable="@color/quantum_panel_bg_color" />
+</ripple>
diff --git a/res/drawable-xhdpi/ic_arrow_back_grey.png b/res/drawable-xhdpi/ic_arrow_back_grey.png
index 79b9b48..11996ef 100755
--- a/res/drawable-xhdpi/ic_arrow_back_grey.png
+++ b/res/drawable-xhdpi/ic_arrow_back_grey.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_search_grey.png b/res/drawable-xhdpi/ic_search_grey.png
index bd5fdf4..e83891c 100755
--- a/res/drawable-xhdpi/ic_search_grey.png
+++ b/res/drawable-xhdpi/ic_search_grey.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_arrow_back_grey.png b/res/drawable-xxhdpi/ic_arrow_back_grey.png
index 8e42e09..ccd3900 100755
--- a/res/drawable-xxhdpi/ic_arrow_back_grey.png
+++ b/res/drawable-xxhdpi/ic_arrow_back_grey.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_search_grey.png b/res/drawable-xxhdpi/ic_search_grey.png
index 1d5c913..f4c5e27 100755
--- a/res/drawable-xxhdpi/ic_search_grey.png
+++ b/res/drawable-xxhdpi/ic_search_grey.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_arrow_back_grey.png b/res/drawable-xxxhdpi/ic_arrow_back_grey.png
index 854a9bd..79b9b48 100755
--- a/res/drawable-xxxhdpi/ic_arrow_back_grey.png
+++ b/res/drawable-xxxhdpi/ic_arrow_back_grey.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_search_grey.png b/res/drawable-xxxhdpi/ic_search_grey.png
index 28519fd..bd5fdf4 100755
--- a/res/drawable-xxxhdpi/ic_search_grey.png
+++ b/res/drawable-xxxhdpi/ic_search_grey.png
Binary files differ
diff --git a/res/drawable/all_apps_search_market_bg.xml b/res/drawable/all_apps_search_market_bg.xml
new file mode 100644
index 0000000..5278e00
--- /dev/null
+++ b/res/drawable/all_apps_search_market_bg.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_focused="true" android:drawable="@color/all_apps_search_market_button_focused_bg_color" />
+ <item android:state_pressed="true" android:drawable="@color/all_apps_search_market_button_focused_bg_color" />
+ <item android:drawable="@android:color/transparent" />
+</selector>
diff --git a/res/drawable/horizontal_line.xml b/res/drawable/horizontal_line.xml
new file mode 100644
index 0000000..3f3f17e3
--- /dev/null
+++ b/res/drawable/horizontal_line.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <size android:height="1dp" />
+ <solid android:color="#ddd" />
+</shape>
diff --git a/res/layout-land/launcher.xml b/res/layout-land/launcher.xml
index 6500ebc..f431fb1 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">
@@ -68,7 +68,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 d0772ee..a7f851e 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">
@@ -78,7 +78,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 802922e..319a493 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">
@@ -76,7 +76,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/all_apps_empty_search.xml b/res/layout/all_apps_empty_search.xml
index f60c4a0..b9b493e 100644
--- a/res/layout/all_apps_empty_search.xml
+++ b/res/layout/all_apps_empty_search.xml
@@ -18,11 +18,14 @@
android:id="@+id/empty_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:gravity="center"
- android:paddingTop="24dp"
- android:paddingBottom="24dp"
- android:paddingRight="@dimen/all_apps_grid_view_start_margin"
- android:textSize="16sp"
- android:textColor="#4c4c4c"
+ android:gravity="start"
+ android:paddingTop="20dp"
+ android:paddingBottom="8dp"
+ android:paddingLeft="16dp"
+ android:paddingRight="16dp"
+ android:fontFamily="sans-serif-medium"
+ android:textSize="14sp"
+ android:textColor="#212121"
+ android:alpha="0.56"
android:focusable="false" />
diff --git a/res/layout/all_apps_search_bar.xml b/res/layout/all_apps_search_bar.xml
index cf30eac..69a66c8 100644
--- a/res/layout/all_apps_search_bar.xml
+++ b/res/layout/all_apps_search_bar.xml
@@ -32,14 +32,13 @@
android:id="@+id/dismiss_search_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginLeft="4dp"
- android:layout_marginStart="4dp"
+ android:layout_gravity="center_vertical"
+ android:layout_marginLeft="16dp"
+ android:layout_marginStart="16dp"
android:contentDescription="@string/all_apps_button_label"
- android:paddingBottom="13dp"
- android:paddingTop="13dp"
android:src="@drawable/ic_arrow_back_grey" />
- <com.android.launcher3.allapps.AllAppsSearchEditView
+ <com.android.launcher3.ExtendedEditText
android:id="@+id/search_box_input"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -48,7 +47,7 @@
android:gravity="fill_horizontal|center_vertical"
android:hint="@string/all_apps_search_bar_hint"
android:inputType="text|textNoSuggestions|textCapWords"
- android:imeOptions="actionDone|flagNoExtractUi"
+ android:imeOptions="actionSearch|flagNoExtractUi"
android:maxLines="1"
android:paddingLeft="8dp"
android:scrollHorizontally="true"
@@ -63,10 +62,8 @@
android:layout_width="wrap_content"
android:layout_height="@dimen/all_apps_search_bar_height"
android:layout_gravity="end|center_vertical"
- android:layout_marginEnd="4dp"
- android:layout_marginRight="4dp"
+ android:layout_marginEnd="16dp"
+ android:layout_marginRight="16dp"
android:contentDescription="@string/all_apps_search_bar_hint"
- android:paddingBottom="13dp"
- android:paddingTop="13dp"
android:src="@drawable/ic_search_grey" />
</FrameLayout>
\ No newline at end of file
diff --git a/res/layout/all_apps_search_market.xml b/res/layout/all_apps_search_market.xml
new file mode 100644
index 0000000..1282069
--- /dev/null
+++ b/res/layout/all_apps_search_market.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+<TextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/search_market_text"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:gravity="start|center_vertical"
+ android:paddingLeft="16dp"
+ android:paddingRight="16dp"
+ android:fontFamily="sans-serif-medium"
+ android:textSize="14sp"
+ android:textColor="#009688"
+ android:textAllCaps="true"
+ android:focusable="false"
+ android:background="@drawable/all_apps_search_market_bg" />
diff --git a/res/layout/all_apps_search_market_divider.xml b/res/layout/all_apps_search_market_divider.xml
new file mode 100644
index 0000000..3909781
--- /dev/null
+++ b/res/layout/all_apps_search_market_divider.xml
@@ -0,0 +1,27 @@
+<?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.
+-->
+<ImageView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:paddingTop="16dp"
+ android:paddingBottom="8dp"
+ android:paddingLeft="16dp"
+ android:paddingRight="16dp"
+ android:focusable="false"
+ android:scaleType="matrix"
+ android:src="@drawable/horizontal_line" />
\ No newline at end of file
diff --git a/res/layout/user_folder.xml b/res/layout/user_folder.xml
index ecf7def..275840d 100644
--- a/res/layout/user_folder.xml
+++ b/res/layout/user_folder.xml
@@ -53,7 +53,7 @@
android:paddingLeft="8dp"
android:paddingRight="8dp" >
- <com.android.launcher3.FolderEditText
+ <com.android.launcher3.ExtendedEditText
android:id="@+id/folder_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 7004524..78be3f4 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Deursoek programme"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Laai tans programme …"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Geen programme gevind wat met \"<xliff:g id="QUERY">%1$s</xliff:g>\" ooreenstem nie"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"Gaan na <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"Niks meer spasie op die tuisskerm nie."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Geen plek meer in die Gunstelinge-laai nie"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Programme"</string>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index d7fed9d..b5d982e 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"መተግበሪያዎችን ይፈልጉ"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"መተግበሪያዎችን በመጫን ላይ..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"ከ«<xliff:g id="QUERY">%1$s</xliff:g>» ጋር የሚዛመዱ ምንም መተግበሪያዎች አልተገኙም"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"ወደ <xliff:g id="QUERY">%1$s</xliff:g> ሂድ"</string>
<string name="out_of_space" msgid="4691004494942118364">"በዚህ መነሻ ማያ ገጽ ላይ ምንም ቦታ የለም።"</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"በተወዳጆች መሣቢያ ውስጥ ተጨማሪ ቦታ የለም"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"መተግበሪያዎች"</string>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index b7cd901..619e11d 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -32,12 +32,13 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"البحث في التطبيقات"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"جارٍ تحميل التطبيقات…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"لم يتم العثور على أية تطبيقات تتطابق مع \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"الانتقال إلى <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"ليس هناك مساحة أخرى في هذه الشاشة الرئيسية."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"لا يوجد المزيد من الحقول في علبة المفضلة"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"التطبيقات"</string>
<string name="all_apps_home_button_label" msgid="252062713717058851">"الرئيسية"</string>
<string name="delete_target_label" msgid="1822697352535677073">"إزالة"</string>
- <string name="delete_target_uninstall_label" msgid="5100785476250872595">"إزالة"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"إلغاء التثبيت"</string>
<string name="info_target_label" msgid="8053346143994679532">"معلومات عن التطبيق"</string>
<string name="permlab_install_shortcut" msgid="5632423390354674437">"تثبيت اختصارات"</string>
<string name="permdesc_install_shortcut" msgid="923466509822011139">"للسماح لتطبيق ما بإضافة اختصارات بدون تدخل المستخدم."</string>
diff --git a/res/values-az-rAZ/strings.xml b/res/values-az-rAZ/strings.xml
index 099a220..b9ea83f 100644
--- a/res/values-az-rAZ/strings.xml
+++ b/res/values-az-rAZ/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Tətbiq Axtarın"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Tətbiqlər endirilir..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" sorğusuna uyğun Tətbiqlər tapılmadı"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g> daxil olun"</string>
<string name="out_of_space" msgid="4691004494942118364">"Bu Əsas ekranda boş yer yoxdur."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Favoritlər-də yer yoxdur"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Tətbiqlər"</string>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index eeaa1ed..214c64b 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Търсене в приложенията"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Приложенията се зареждат…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Няма намерени приложения, съответстващи на „<xliff:g id="QUERY">%1$s</xliff:g>“"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"Отваряне на <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"На този начален екран няма повече място."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Няма повече място в областта с любимите"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Приложения"</string>
diff --git a/res/values-bn-rBD/strings.xml b/res/values-bn-rBD/strings.xml
index 57f7b51..129d250 100644
--- a/res/values-bn-rBD/strings.xml
+++ b/res/values-bn-rBD/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"অ্যাপ্লিকেশানগুলি অনুসন্ধান করুন"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"অ্যাপ্লিকেশানগুলি লোড হচ্ছে..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" এর সাথে মেলে এমন কোনো অ্যাপ্লিকেশান পাওয়া যায়নি"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g> এ যান"</string>
<string name="out_of_space" msgid="4691004494942118364">"এই হোম স্ক্রীনে আর কোনো জায়গা নেই৷"</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"পছন্দসই ট্রে-তে আর কোনো জায়গা নেই"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"অ্যাপ্লিকেশানগুলি"</string>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 83f284a..cf874a4 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Cerca a les aplicacions"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"S\'estan carregant les aplicacions..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"No s\'ha trobat cap aplicació que coincideixi amb <xliff:g id="QUERY">%1$s</xliff:g>"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"Vés a <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"Ja no queda espai en aquesta pantalla d\'inici."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"No hi ha més espai a la safata Preferits."</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Aplicacions"</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index c99a7ed..c2f9f42 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Hledat aplikace"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Načítání aplikací…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Dotazu „<xliff:g id="QUERY">%1$s</xliff:g>“ neodpovídají žádné aplikace"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"Přejít na <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"Na této ploše již není místo."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Na panelu Oblíbené položky již není místo."</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Aplikace"</string>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index eeab92e..a948705 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Søg i Apps"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Indlæser apps…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Der blev ikke fundet nogen apps, som matcher \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"Gå til <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"Der er ikke mere plads på denne startskærm."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Der er ikke mere plads i bakken Foretrukne"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Apps"</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index ac6f76b..96ae74b 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"In Apps suchen"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Apps werden geladen..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Keine Apps für \"<xliff:g id="QUERY">%1$s</xliff:g>\" gefunden"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"Gehe zu <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"Auf diesem Startbildschirm ist kein Platz mehr vorhanden."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Ablage \"Favoriten\" ist voll."</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Apps"</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index a60458a..81b43e2 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Αναζήτηση εφαρμογών"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Φόρτωση εφαρμογών…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Δεν βρέθηκαν εφαρμογές για το ερώτημα \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"Μετάβαση σε <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"Δεν υπάρχει χώρος σε αυτήν την αρχική οθόνη."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Δεν υπάρχει επιπλέον χώρος στην περιοχή Αγαπημένα"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Εφαρμογές"</string>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index 09963e4..f2a6eab 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Search Apps"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Loading Apps…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"No Apps found matching \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"Go to <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"No more room on this Home screen."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"No more room in the Favourites tray"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Apps"</string>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index 09963e4..f2a6eab 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Search Apps"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Loading Apps…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"No Apps found matching \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"Go to <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"No more room on this Home screen."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"No more room in the Favourites tray"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Apps"</string>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index 09963e4..f2a6eab 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Search Apps"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Loading Apps…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"No Apps found matching \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"Go to <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"No more room on this Home screen."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"No more room in the Favourites tray"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Apps"</string>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 6366742..c754493 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Buscar aplicaciones"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Cargando aplicaciones…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"No hay aplicaciones que coincidan con <xliff:g id="QUERY">%1$s</xliff:g>."</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"Ir a <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"No hay más espacio en esta pantalla principal."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"La bandeja de favoritos está llena."</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Aplicaciones"</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 34331fc..16cd84f 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Busca aplicaciones"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Cargando aplicaciones…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"No se han encontrado aplicaciones que contengan \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"Ir a <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"No queda espacio en la pantalla de inicio."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"La bandeja de favoritos está completa"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Aplicaciones"</string>
diff --git a/res/values-et-rEE/strings.xml b/res/values-et-rEE/strings.xml
index c4ba823..84ca45a 100644
--- a/res/values-et-rEE/strings.xml
+++ b/res/values-et-rEE/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Otsige rakendustest"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Rakenduste laadimine ..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Päringule „<xliff:g id="QUERY">%1$s</xliff:g>” ei vastanud ükski rakendus"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"Mine: <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"Sellel avaekraanil pole enam ruumi."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Salves Lemmikud pole rohkem ruumi"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Rakendused"</string>
diff --git a/res/values-eu-rES/strings.xml b/res/values-eu-rES/strings.xml
index 8bfca6c..096a4f9 100644
--- a/res/values-eu-rES/strings.xml
+++ b/res/values-eu-rES/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Bilatu aplikazioetan"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Aplikazioak kargatzen…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Ez da aurkitu \"<xliff:g id="QUERY">%1$s</xliff:g>\" bilaketarekin bat datorren aplikaziorik"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"Joan hona: <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"Hasierako pantaila honetan ez dago toki gehiago."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Ez dago toki gehiago Gogokoak erretiluan"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Aplikazioak"</string>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index 701d59d..3b6d01a 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"جستجوی برنامهها"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"در حال بارگیری برنامهها..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"هیچ برنامهای مطابق با «<xliff:g id="QUERY">%1$s</xliff:g>» پیدا نشد"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"رفتن به <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"فضای بیشتری در این صفحه اصلی موجود نیست."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"فضای بیشتری در سینی موارد دلخواه وجود ندارد"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"برنامهها"</string>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index f0766d6..47b580e 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Sovellushaku"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Ladataan sovelluksia…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"”<xliff:g id="QUERY">%1$s</xliff:g>” ei palauttanut sovelluksia."</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"Siirry: <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"Tässä aloitusruudussa ei ole enää tilaa."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Suosikit-valikossa ei ole enää tilaa"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Sovellukset"</string>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index 0c19ef2..91271f7 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Rechercher des applications"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Chargement des applications en cours..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Aucune application trouvée correspondant à « <xliff:g id="QUERY">%1$s</xliff:g> »"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"Aller à « <xliff:g id="QUERY">%1$s</xliff:g> »"</string>
<string name="out_of_space" msgid="4691004494942118364">"Pas d\'espace libre sur l\'écran d\'accueil."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Il n\'y a plus d\'espace dans la zone des favoris"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Applications"</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 662bcf1..84e1594 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Rechercher dans les applications"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Chargement des applications en cours…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Aucune application ne correspond à la requête \"<xliff:g id="QUERY">%1$s</xliff:g>\"."</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"Accéder à <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"Pas d\'espace libre sur cet écran d\'accueil."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Plus d\'espace disponible dans la zone de favoris."</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Applications"</string>
diff --git a/res/values-gl-rES/strings.xml b/res/values-gl-rES/strings.xml
index 6cf76a7..ccfe21e 100644
--- a/res/values-gl-rES/strings.xml
+++ b/res/values-gl-rES/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Aplicacións de busca"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Cargando aplicacións..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Non se atoparon aplicacións que coincidan con \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"Ir a <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"Non hai máis espazo nesta pantalla de inicio."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Non hai máis espazo na bandexa de favoritos"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Aplicacións"</string>
diff --git a/res/values-gu-rIN/strings.xml b/res/values-gu-rIN/strings.xml
index 4a2274f..db056a0 100644
--- a/res/values-gu-rIN/strings.xml
+++ b/res/values-gu-rIN/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"શોધ એપ્લિકેશનો"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"એપ્લિકેશનો લોડ કરી રહ્યું છે…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" થી મેળ ખાતી કોઈ એપ્લિકેશનો મળી નથી"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g> પર જાઓ"</string>
<string name="out_of_space" msgid="4691004494942118364">"આ હોમ સ્ક્રીન પર વધુ જગ્યા નથી."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"મનપસંદ ટ્રે પર વધુ જગ્યા નથી"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"એપ્લિકેશનો"</string>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 6fdbf24..7cf4f33 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"ऐप्स खोजें"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"ऐप्स लोड हो रहे हैं..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" से मिलान करने वाला कोई ऐप नहीं मिला"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g> पर जाएं"</string>
<string name="out_of_space" msgid="4691004494942118364">"इस होम स्क्रीन पर स्थान शेष नहीं है."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"पसंदीदा ट्रे में और स्थान नहीं है"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"ऐप्लिकेशन"</string>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index a54a7e4..2679570 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Pretraži aplikacije"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Učitavanje aplikacija…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Nema aplikacija podudarnih s upitom \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"Idite na <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"Na ovom početnom zaslonu više nema mjesta."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Nema više prostora na traci Favoriti"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Aplikacije"</string>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index e9fc574..41c38b2 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Alkalmazások keresése"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Alkalmazások betöltése…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Egy alkalmazás sem található a(z) „<xliff:g id="QUERY">%1$s</xliff:g>” lekérdezésre."</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"Keresse fel ezt: <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"Nincs több hely ezen a kezdőképernyőn."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Nincs több hely a Kedvencek tálcán"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Alkalmazások"</string>
diff --git a/res/values-hy-rAM/strings.xml b/res/values-hy-rAM/strings.xml
index de4aed1..49a9d18 100644
--- a/res/values-hy-rAM/strings.xml
+++ b/res/values-hy-rAM/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Հավելվածների որոնում"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Հավելվածների բեռնում…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"«<xliff:g id="QUERY">%1$s</xliff:g>» հարցմանը համապատասխանող հավելվածներ չեն գտնվել"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"Գնալ <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"Այլևս տեղ չկա այս հիմնական էկրանին:"</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Ընտրյալների ցուցակում այլևս ազատ տեղ չկա"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Ծրագրեր"</string>
@@ -59,11 +60,11 @@
<string name="workspace_cling_longpress_title" msgid="9173998993909018310">"Պաստառներ, վիջեթներ և կարգավորումներ"</string>
<string name="workspace_cling_longpress_description" msgid="4119994475505235248">"Հարմարեցնելու համար հպեք և պահեք հետնաշերտի վրա"</string>
<string name="workspace_cling_longpress_dismiss" msgid="368660286867640874">"ՀԱՍԿԱՆԱԼԻ Է"</string>
- <string name="folder_opened" msgid="94695026776264709">"Թղթապանակը բաց է, <xliff:g id="WIDTH">%1$d</xliff:g>-ից <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_opened" msgid="94695026776264709">"Պանակը բաց է, <xliff:g id="WIDTH">%1$d</xliff:g>-ից <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
<string name="folder_tap_to_close" msgid="1884479294466410023">"Հպեք՝ պանակը փակելու համար"</string>
<string name="folder_tap_to_rename" msgid="9191075570492871147">"Հպեք՝ վերանվանումը պահելու համար"</string>
- <string name="folder_closed" msgid="4100806530910930934">"Թղթապանակը փակ է"</string>
- <string name="folder_renamed" msgid="1794088362165669656">"Թղթապանակը վերանվանվեց <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"Պանակը փակ է"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"Պանակը վերանվանվեց <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format" msgid="6629239338071103179">"Թղթապանակ՝ <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="widget_button_text" msgid="2880537293434387943">"Վիջեթներ"</string>
<string name="wallpaper_button_text" msgid="8404103075899945851">"Պաստառներ"</string>
@@ -87,7 +88,7 @@
<string name="add_to_folder_with_app" msgid="4534929978967147231">"Ավելացնել «<xliff:g id="NAME">%1$s</xliff:g>» պանակին"</string>
<string name="added_to_folder" msgid="4793259502305558003">"Տարրն ավելացվեց թղթապանակում"</string>
<string name="create_folder_with" msgid="4050141361160214248">"Ստեղծել թղթապանակ, օգտագործելով՝ <xliff:g id="NAME">%1$s</xliff:g>"</string>
- <string name="folder_created" msgid="6409794597405184510">"Թղթապանակը ստեղծվեց"</string>
+ <string name="folder_created" msgid="6409794597405184510">"Պանակը ստեղծվեց"</string>
<string name="action_move_to_workspace" msgid="1603837886334246317">"Տեղափոխել Հիմնական էկրան"</string>
<string name="action_move_screen_left" msgid="8854216831569401665">"Տեղափոխել էկրանը ձախ"</string>
<string name="action_move_screen_right" msgid="329334910274311123">"Տեղափոխել էկրանը աջ"</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index c65254a..21c2080 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Telusuri Apps"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Memuat Aplikasi..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Tidak ditemukan Aplikasi yang cocok dengan \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"Buka <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"Tidak ada ruang lagi pada layar Utama ini."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Tidak ada ruang tersisa di baki Favorit"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Aplikasi"</string>
diff --git a/res/values-is-rIS/strings.xml b/res/values-is-rIS/strings.xml
index 1d46cdc..7422eaf 100644
--- a/res/values-is-rIS/strings.xml
+++ b/res/values-is-rIS/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Leita í forritum"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Hleður forrit…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Ekki fundust forrit sem samsvara „<xliff:g id="QUERY">%1$s</xliff:g>“"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"Fara í <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"Ekki meira pláss á þessum heimaskjá."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Ekki meira pláss í bakka fyrir uppáhald"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Forrit"</string>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 02057a1..e1e75b3 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Cerca app"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Caricamento di app…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Nessuna app trovata corrispondente a \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"Vai a <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"Spazio nella schermata Home esaurito."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Spazio esaurito nella barra dei Preferiti"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"App"</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index a8039f7..3686abd 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"חפש אפליקציות"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"טוען אפליקציות…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"לא נמצאו אפליקציות התואמות ל-\"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"עבור אל <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"אין עוד מקום במסך דף הבית הזה."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"אין עוד מקום במגש המועדפים"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"אפליקציות"</string>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index ba58d88..87b3fa4 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"アプリを検索"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"アプリを読み込んでいます…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"「<xliff:g id="QUERY">%1$s</xliff:g>」に一致するアプリは見つかりませんでした"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g>にアクセス"</string>
<string name="out_of_space" msgid="4691004494942118364">"このホーム画面に空きスペースがありません。"</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"お気に入りトレイに空きスペースがありません"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"アプリ"</string>
@@ -57,7 +58,7 @@
<string name="migration_cling_copy_apps" msgid="946331230090919440">"アイコンをコピー"</string>
<string name="migration_cling_use_default" msgid="2626475813981258626">"初期状態にリセットする"</string>
<string name="workspace_cling_longpress_title" msgid="9173998993909018310">"壁紙、ウィジェット、設定"</string>
- <string name="workspace_cling_longpress_description" msgid="4119994475505235248">"カスタマイズするにはバックグラウンドを押し続けます"</string>
+ <string name="workspace_cling_longpress_description" msgid="4119994475505235248">"カスタマイズするには背景を押し続けます"</string>
<string name="workspace_cling_longpress_dismiss" msgid="368660286867640874">"OK"</string>
<string name="folder_opened" msgid="94695026776264709">"フォルダが開いています。<xliff:g id="WIDTH">%1$d</xliff:g>x<xliff:g id="HEIGHT">%2$d</xliff:g>の大きさです"</string>
<string name="folder_tap_to_close" msgid="1884479294466410023">"タップしてフォルダを閉じます"</string>
diff --git a/res/values-ka-rGE/strings.xml b/res/values-ka-rGE/strings.xml
index 07030d8..2900330 100644
--- a/res/values-ka-rGE/strings.xml
+++ b/res/values-ka-rGE/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"აპების ძიება"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"აპები იტვირთება..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"„<xliff:g id="QUERY">%1$s</xliff:g>“-ის თანხვედრი აპები არ მოიძებნა"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"გადადი <xliff:g id="QUERY">%1$s</xliff:g>-ში"</string>
<string name="out_of_space" msgid="4691004494942118364">"ამ მთავარ ეკრანზე ადგილი აღარ არის."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"რჩეულების თაროზე ადგილი არ არის"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"აპები"</string>
diff --git a/res/values-kk-rKZ/strings.xml b/res/values-kk-rKZ/strings.xml
index e79c9ce..cae5e5e 100644
--- a/res/values-kk-rKZ/strings.xml
+++ b/res/values-kk-rKZ/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Қолданбаларды іздеу"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Қолданбалар жүктелуде…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"«<xliff:g id="QUERY">%1$s</xliff:g>» сұрауына сәйкес келетін қолданбалар жоқ"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g> сұрауына өту"</string>
<string name="out_of_space" msgid="4691004494942118364">"Бұл Негізгі экранда орын қалмады."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Қалаулылар науасында орын қалмады"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Қолданбалар"</string>
diff --git a/res/values-km-rKH/strings.xml b/res/values-km-rKH/strings.xml
index 9f7f537..5ffbf76 100644
--- a/res/values-km-rKH/strings.xml
+++ b/res/values-km-rKH/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"ស្វែងរកកម្មវិធី"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"កំពុងដំណើរការកម្មវិធី..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"គ្មានកម្មវិធីដែលត្រូវជាមួយ \"<xliff:g id="QUERY">%1$s</xliff:g>\" ទេ"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"ចូលទៅកាន់ <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"គ្មានបន្ទប់នៅលើអេក្រង់ដើមនេះទៀតទេ។"</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"គ្មានបន្ទប់ក្នុងថាសនិយមប្រើ"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"កម្មវិធី"</string>
diff --git a/res/values-kn-rIN/strings.xml b/res/values-kn-rIN/strings.xml
index 5cd8792..a1aab7b 100644
--- a/res/values-kn-rIN/strings.xml
+++ b/res/values-kn-rIN/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"ಅಪ್ಲಿಕೇಷನ್ಗಳನ್ನು ಹುಡುಕಿ"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"ಅಪ್ಲಿಕೇಶನ್ಗಳನ್ನು ಲೋಡ್ ಮಾಡಲಾಗುತ್ತಿದೆ..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" ಹೊಂದಿಕೆಯ ಯಾವುದೇ ಅಪ್ಲಿಕೇಶನ್ಗಳು ಕಂಡುಬಂದಿಲ್ಲ"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g> ಗೆ ಹೋಗಿ"</string>
<string name="out_of_space" msgid="4691004494942118364">"ಈ ಮುಖಪುಟದ ಪರದೆಯಲ್ಲಿ ಹೆಚ್ಚು ಸ್ಥಳಾವಕಾಶವಿಲ್ಲ."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"ಮೆಚ್ಚಿನವುಗಳ ಟ್ರೇನಲ್ಲಿ ಹೆಚ್ಚಿನ ಸ್ಥಳಾವಕಾಶವಿಲ್ಲ"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"ಅಪ್ಲಿಕೇಶನ್ಗಳು"</string>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index fa445ac..e069712 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"앱 검색"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"앱 로드 중..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"\'<xliff:g id="QUERY">%1$s</xliff:g>\'와(과) 일치하는 앱이 없습니다."</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g>(으)로 이동"</string>
<string name="out_of_space" msgid="4691004494942118364">"홈 화면에 더 이상 공간이 없습니다."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"즐겨찾기 트레이에 더 이상 공간이 없습니다."</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"앱"</string>
diff --git a/res/values-ky-rKG/strings.xml b/res/values-ky-rKG/strings.xml
index 2078748..c530b5c 100644
--- a/res/values-ky-rKG/strings.xml
+++ b/res/values-ky-rKG/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Колдонмолорду издөө"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Колдонмолор жүктөлүүдө…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" дал келген колдонмолор табылган жок"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g> сурамына өтүңүз"</string>
<string name="out_of_space" msgid="4691004494942118364">"Бул Үй экранында бош орун жок."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Тандамалдар тайпасында орун калган жок"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Колдонмолор"</string>
diff --git a/res/values-lo-rLA/strings.xml b/res/values-lo-rLA/strings.xml
index dff2612..005f026 100644
--- a/res/values-lo-rLA/strings.xml
+++ b/res/values-lo-rLA/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"ຊອກຫາແອັບ"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"ກຳລັງໂຫລດແອັບ..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"ບໍ່ພົບແອັບໃດທີ່ກົງກັນ \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"ໄປທີ່ <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"ບໍ່ມີຫ້ອງເຫຼືອໃນໜ້າຈໍຫຼັກນີ້."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"ບໍ່ມີບ່ອນຫວ່າງໃນຖາດສຳລັບເກັບສິ່ງທີ່ໃຊ້ເປັນປະຈຳ"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"ແອັບຯ"</string>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index b5c0832..442874d 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Ieškoti programų"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Įkeliamos programos..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Nerasta jokių užklausą „<xliff:g id="QUERY">%1$s</xliff:g>“ atitinkančių programų"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"Eiti į <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"Šiame pagrindiniame ekrane vietos nebėra."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Mėgstamiausių dėkle nebėra vietos"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Programos"</string>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 432bda4..1bef22e 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Meklēt lietotnes"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Notiek lietotņu ielāde…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Vaicājumam “<xliff:g id="QUERY">%1$s</xliff:g>” neatbilda neviena lietotne."</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"Doties uz: <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"Šajā sākuma ekrānā vairs nav vietas."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Izlases joslā vairs nav vietas."</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Lietotnes"</string>
diff --git a/res/values-mk-rMK/strings.xml b/res/values-mk-rMK/strings.xml
index 8c276e3..0a6704b 100644
--- a/res/values-mk-rMK/strings.xml
+++ b/res/values-mk-rMK/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Пребарување апликации"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Се вчитуваат апликации…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Не се најдени апликации што одговараат на „<xliff:g id="QUERY">%1$s</xliff:g>“"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"Оди на <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"Нема повеќе простор на овој екран на почетната страница."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Нема повеќе простор на лентата „Омилени“"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Апликации"</string>
diff --git a/res/values-ml-rIN/strings.xml b/res/values-ml-rIN/strings.xml
index 59ad153..2bd80d3 100644
--- a/res/values-ml-rIN/strings.xml
+++ b/res/values-ml-rIN/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"ആപ്പ്സ് തിരയുക"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"ആപ്പ്സ് ലോഡുചെയ്യുന്നു..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" എന്നതുമായി പൊരുത്തപ്പെടുന്ന ആപ്പ്സൊന്നും കണ്ടെത്തിയില്ല"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g> എന്നതിലേക്ക് പോവുക"</string>
<string name="out_of_space" msgid="4691004494942118364">"ഈ ഹോം സ്ക്രീനിൽ ഒഴിവൊന്നുമില്ല."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"പ്രിയപ്പെട്ടവയുടെ ട്രേയിൽ ഒഴിവൊന്നുമില്ല"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"അപ്ലിക്കേഷനുകൾ"</string>
diff --git a/res/values-mn-rMN/strings.xml b/res/values-mn-rMN/strings.xml
index fafb37b..19ba66f 100644
--- a/res/values-mn-rMN/strings.xml
+++ b/res/values-mn-rMN/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Апп хайх"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Аппликейшныг ачаалж байна..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\"-тай нийцэх апп олдсонгүй"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g> руу очих"</string>
<string name="out_of_space" msgid="4691004494942118364">"Энэ Нүүр дэлгэц зайгүй."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"\"Дуртай\" трей дээр өөр зай байхгүй байна"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Апп"</string>
diff --git a/res/values-mr-rIN/strings.xml b/res/values-mr-rIN/strings.xml
index 657010d..dc29b54 100644
--- a/res/values-mr-rIN/strings.xml
+++ b/res/values-mr-rIN/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"अॅप्स शोधा"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"अॅप्स लोड करीत आहे..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" शी जुळणारे कोणतेही अॅप्स आढळले नाहीत"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g> वर जा"</string>
<string name="out_of_space" msgid="4691004494942118364">"या मुख्य स्क्रीनवर आणखी जागा नाही."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"आवडीच्या ट्रे मध्ये आणखी जागा नाही"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"अॅप्स"</string>
diff --git a/res/values-ms-rMY/strings.xml b/res/values-ms-rMY/strings.xml
index 0c6a5f5..26f2748 100644
--- a/res/values-ms-rMY/strings.xml
+++ b/res/values-ms-rMY/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Cari Apl"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Memuatkan Apl…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Tiada Apl yang ditemui sepadan dengan \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"Pergi ke <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"Tiada lagi ruang pada skrin Laman Utama ini."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Tiada ruang dalam dulang Kegemaran lagi"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Apl"</string>
diff --git a/res/values-my-rMM/strings.xml b/res/values-my-rMM/strings.xml
index 19ec3a6..c13ff7e 100644
--- a/res/values-my-rMM/strings.xml
+++ b/res/values-my-rMM/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"ရှာဖွေမှု Appများ"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"App များ ရယူနေစဉ်..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" နှင့်ကိုက်ညီသည့် အပ်ဖ်များမတွေ့ပါ"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g>ကို သွားပါ"</string>
<string name="out_of_space" msgid="4691004494942118364">"ဤပင်မမျက်နှာစာတွင် နေရာလွတ် မကျန်တော့ပါ"</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"အနှစ်သက်ဆုံးများ ထားရာတွင် နေရာလွတ် မကျန်တော့ပါ"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"အပ်ပလီကေးရှင်းများ"</string>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index 21b87c8..198d56c 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Søk i apper"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Laster inn apper …"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Fant ingen apper som samsvarer med «<xliff:g id="QUERY">%1$s</xliff:g>»"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"Gå til <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"Denne startsiden er full."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Favoritter-skuffen er full"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Apper"</string>
diff --git a/res/values-ne-rNP/strings.xml b/res/values-ne-rNP/strings.xml
index dd3b3f3..472bd4f 100644
--- a/res/values-ne-rNP/strings.xml
+++ b/res/values-ne-rNP/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"अनुप्रयोगहरू खोज्नुहोस्"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"अनुप्रयोगहरू लोड गरिँदै..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" सँग मिल्दो कुनै अनुप्रयोगहरू फेला परेनन्"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g> मा जानुहोस्"</string>
<string name="out_of_space" msgid="4691004494942118364">"यो गृह स्क्रिनमा कुनै थप ठाउँ छैन।"</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"मनपर्ने ट्रे अब कुनै ठाँउ छैन"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"अनुप्रयोगहरू"</string>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index fcbe375..6f49810 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Apps zoeken"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Apps laden…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Er zijn geen apps gevonden die overeenkomen met \'<xliff:g id="QUERY">%1$s</xliff:g>\'"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"Ga naar <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"Er is geen ruimte meer op dit startscherm."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Geen ruimte meer in het vak \'Favorieten\'"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Apps"</string>
diff --git a/res/values-pa-rIN/strings.xml b/res/values-pa-rIN/strings.xml
index d510dc7..30c4428 100644
--- a/res/values-pa-rIN/strings.xml
+++ b/res/values-pa-rIN/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"ਐਪਸ ਖੋਜੋ"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"ਐਪਸ ਲੋਡ ਕਰ ਰਿਹਾ ਹੈ..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" ਨਾਲ ਮਿਲਦੀ ਕੋਈ ਵੀ ਐਪਸ ਨਹੀਂ ਲੱਭੀਆਂ"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g> ਤੇ ਜਾਓ"</string>
<string name="out_of_space" msgid="4691004494942118364">"ਇਸ ਹੋਮ ਸਕ੍ਰੀਨ ਲਈ ਹੋਰ ਖਾਲੀ ਸਥਾਨ ਨਹੀਂ ਹੈ।"</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"ਮਨਪਸੰਦ ਟ੍ਰੇ ਵਿੱਚ ਹੋਰ ਖਾਲੀ ਸਥਾਨ ਨਹੀਂ।"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"ਐਪਸ"</string>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index b3b761d..a1fd29f 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Szukaj w aplikacjach"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Wczytuję aplikacje…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Nie znaleziono aplikacji pasujących do zapytania „<xliff:g id="QUERY">%1$s</xliff:g>”"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"Otwórz <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"Brak miejsca na tym ekranie głównym."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Brak miejsca w Ulubionych"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Aplikacje"</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 9140b62..0d8a431 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Pesquisar aplicações"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"A carregar aplicações..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Não foram encontradas aplic. que correspondam a \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"Aceder a <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"Sem espaço suficiente neste Ecrã principal."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Não existe mais espaço no tabuleiro de Favoritos"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Aplicações"</string>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index c8e6458..41e5c02 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Pesquisar apps"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Carregando apps…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Nenhum app encontrado que corresponda a \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"Ir para <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"Não há mais espaço na tela inicial."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Sem espaço na bandeja de favoritos"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Apps"</string>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 88cbc79..ffaca78 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Căutați aplicații"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Se încarcă aplicațiile..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Nu s-a găsit nicio aplicație pentru „<xliff:g id="QUERY">%1$s</xliff:g>”"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"Accesați <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"Nu mai este loc pe acest Ecran de pornire."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Spațiu epuizat în bara Preferate"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Aplicații"</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 5b492fe..6e69e4d 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Поиск приложений"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Загрузка…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"По запросу \"<xliff:g id="QUERY">%1$s</xliff:g>\" ничего не найдено"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"На этом экране все занято"</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"В разделе \"Избранное\" больше нет места"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Приложения"</string>
diff --git a/res/values-si-rLK/strings.xml b/res/values-si-rLK/strings.xml
index 68e3178..476fff8 100644
--- a/res/values-si-rLK/strings.xml
+++ b/res/values-si-rLK/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"යෙදුම් සෙවීම"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"යෙදුම් පූරණය වෙමින්…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" සමග ගැළපෙන යෙදුම් හමු නොවිණි"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g> වෙත යන්න"</string>
<string name="out_of_space" msgid="4691004494942118364">"මෙම මුල් පිටු තිරය මත තවත් අවසර නැත."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"ප්රියතම දෑ ඇති තැටියේ තවත් ඉඩ නොමැත"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"යෙදුම්"</string>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 1cb76e3..703b46c 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Vyhľadávanie v aplikáciách"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Načítavajú sa aplikácie..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Nenašli sa žiadne aplikácie zodpovedajúce dopytu <xliff:g id="QUERY">%1$s</xliff:g>"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"Prejsť na dopyt <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"Na tejto ploche už nie je miesto"</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Na paneli Obľúbené položky už nie je miesto"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Aplikácie"</string>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 92c69a5..497378b 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Iskanje po aplikacijah"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Nalaganje aplikacij …"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Ni aplikacij, ki bi ustrezale poizvedbi »<xliff:g id="QUERY">%1$s</xliff:g>«"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"Odpri storitev <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"Na tem začetnem zaslonu ni več prostora."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"V vrstici za priljubljene ni več prostora"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Aplikacije"</string>
diff --git a/res/values-sq-rAL/strings.xml b/res/values-sq-rAL/strings.xml
index 65aabf6..2b535a3 100644
--- a/res/values-sq-rAL/strings.xml
+++ b/res/values-sq-rAL/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Kërko për aplikacione"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Po ngarkon aplikacionet..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Nuk u gjet asnjë aplikacion që përputhet me \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"Shko te <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"Nuk ka më hapësirë në këtë ekran bazë."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Nuk ka më hapësirë në tabakanë \"Të preferuarat\""</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Aplikacionet"</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 0f93bf3..f21aae0 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Претражите апликације"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Апликације се учитавају..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Није пронађена ниједна апликација за „<xliff:g id="QUERY">%1$s</xliff:g>“"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"Иди на апликацију <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"Нема више простора на овом почетном екрану."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Нема више простора на траци Омиљено"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Апликације"</string>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 93fce80..385d7a6 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Sök efter appar"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Läser in appar …"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Det gick inte att hitta några appar som matchar <xliff:g id="QUERY">%1$s</xliff:g>"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"Gå till <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"Det finns inte plats för mer på den här startskärmen."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Favoritfältet är fullt"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Appar"</string>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 297b731..b86fb73 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Tafuta Programu"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Inapakia Programu..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Haikupata programu zinazolingana na \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"Nenda kwenye <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"Hakuna nafasi katika skrini hii ya Mwanzo."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Hakuna nafasi zaidi katika treya ya Vipendeleo"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Programu"</string>
diff --git a/res/values-sw600dp/config.xml b/res/values-sw600dp/config.xml
index a7345a7..eb9af97 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 2651fbb..c1ac9aa 100644
--- a/res/values-sw600dp/dimens.xml
+++ b/res/values-sw600dp/dimens.xml
@@ -28,4 +28,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-ta-rIN/strings.xml b/res/values-ta-rIN/strings.xml
index 1f2a6f9..9dac6d0 100644
--- a/res/values-ta-rIN/strings.xml
+++ b/res/values-ta-rIN/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"பயன்பாடுகளில் தேடுக"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"பயன்பாடுகளை ஏற்றுகிறது..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" உடன் பொருந்தும் பயன்பாடுகள் இல்லை"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g>க்குச் செல்லவும்"</string>
<string name="out_of_space" msgid="4691004494942118364">"முகப்புத் திரையில் இடமில்லை."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"பிடித்தவை ட்ரேயில் இடமில்லை"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"பயன்பாடுகள்"</string>
diff --git a/res/values-te-rIN/strings.xml b/res/values-te-rIN/strings.xml
index bd5fd70..32cb292 100644
--- a/res/values-te-rIN/strings.xml
+++ b/res/values-te-rIN/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"అనువర్తనాలను శోధించండి"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"అనువర్తనాలను లోడ్ చేస్తోంది…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\"కి సరిపోలే అనువర్తనాలేవీ కనుగొనబడలేదు"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g>కి వెళ్లు"</string>
<string name="out_of_space" msgid="4691004494942118364">"ఈ హోమ్ స్క్రీన్లో ఖాళీ లేదు."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"ఇష్టమైనవి ట్రేలో ఖాళీ లేదు"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"అనువర్తనాలు"</string>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 85919af..53ae589 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"ค้นหาแอป"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"กำลังโหลดแอป…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"ไม่พบแอปที่ตรงกับ \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"ไปที่ <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"ไม่มีที่ว่างในหน้าจอหลักนี้"</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"ไม่มีพื้นที่เหลือในถาดรายการโปรด"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"แอป"</string>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index 69a5bb7..43afeab 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Mga App sa Paghahanap"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Nilo-load ang Mga App…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Walang nakitang Mga App na tumutugma sa \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"Pumunta sa <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"Wala nang lugar sa Home screen na ito."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Wala nang lugar sa tray ng Mga Paborito"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Apps"</string>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index d933c87..064d049 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Uygulamalarda Ara"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Uygulamalar Yükleniyor…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" ile eşleşen uygulama bulunamadı"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g> uygulamasına git"</string>
<string name="out_of_space" msgid="4691004494942118364">"Bu Ana ekranda yer kalmadı."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Favoriler tepsisinde başka yer kalmadı"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Uygulamalar"</string>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index b06177e..ff9364e 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Пошук додатків"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Завантаження додатків…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Немає додатків для запиту \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"Перейти в додаток <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"На цьому головному екрані більше немає місця."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"В області \"Вибране\" немає місця"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Додатки"</string>
diff --git a/res/values-ur-rPK/strings.xml b/res/values-ur-rPK/strings.xml
index ba189c8..f3d6311 100644
--- a/res/values-ur-rPK/strings.xml
+++ b/res/values-ur-rPK/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"ایپس تلاش کریں"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"ایپس لوڈ ہو رہی ہیں…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" سے مماثل کوئی ایپس نہیں ملیں"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g> پر جائیں"</string>
<string name="out_of_space" msgid="4691004494942118364">"اس ہوم اسکرین پر مزید کوئی گنجائش نہیں ہے۔"</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"پسندیدہ ٹرے میں مزید کوئی گنجائش نہیں ہے"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"ایپس"</string>
diff --git a/res/values-uz-rUZ/strings.xml b/res/values-uz-rUZ/strings.xml
index c3bffeb..24e4c4d 100644
--- a/res/values-uz-rUZ/strings.xml
+++ b/res/values-uz-rUZ/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Ilovalarni qidirish"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Ilovalar yuklanmoqda…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"“<xliff:g id="QUERY">%1$s</xliff:g>” bilan mos hech qanday ilova topilmadi"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"O‘tish: <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"Uy ekranida bitta ham xona yo‘q."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Ajratilganlarda birorta ham xona yo‘q"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Ilovalar"</string>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index f57f491..6eff507 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Tìm kiếm ứng dụng"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Đang tải ứng dụng..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Không tìm thấy ứng dụng nào phù hợp với \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"Chuyển tới <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"Không còn chỗ trên Màn hình chính này."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Không còn chỗ trong khay Mục yêu thích"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Ứng dụng"</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 82274ba..77f07c9 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"搜索应用"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"正在加载应用…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"未找到与“<xliff:g id="QUERY">%1$s</xliff:g>”相符的应用"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"转到 <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"此主屏幕上已没有空间。"</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"收藏栏已满"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"应用"</string>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index f7d240e..62032ec 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"搜尋應用程式"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"正在載入應用程式…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"無法找到與「<xliff:g id="QUERY">%1$s</xliff:g>」相符的應用程式"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"前往 <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"主畫面已無空間。"</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"我的收藏寄存區沒有足夠空間"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"應用程式"</string>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 39a0ec7..0b3f4d6 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"搜尋應用程式"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"正在載入應用程式…"</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"找不到符合「<xliff:g id="QUERY">%1$s</xliff:g>」的應用程式"</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"前往 <xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"這個主螢幕已無空間。"</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"「我的最愛」匣已無可用空間"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"應用程式"</string>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 83ba089..bb49f1f 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -32,6 +32,7 @@
<string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Sesha Izinhlelo Zokusebenza"</string>
<string name="all_apps_loading_message" msgid="7557140873644765180">"Ilayisha izinhlelo zokusebenza..."</string>
<string name="all_apps_no_search_results" msgid="6332185285860416787">"Azikho izinhlelo zokusebenza ezitholakele ezifana ne-\"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+ <string name="all_apps_search_market_message" msgid="5470761048755751471">"Hamba ku-<xliff:g id="QUERY">%1$s</xliff:g>"</string>
<string name="out_of_space" msgid="4691004494942118364">"Asisekho isikhala kulesi sikrini Sasekhaya."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Asisekho isikhala kwitreyi lezintandokazi"</string>
<string name="all_apps_button_label" msgid="9110807029020582876">"Izinhlelo zokusebenza"</string>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 51e4d40..6fe2ae1 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -39,11 +39,12 @@
<color name="outline_color">#FFFFFFFF</color>
<!-- Containers -->
- <color name="container_fastscroll_thumb_inactive_color">#42000000</color>
+ <color name="container_fastscroll_thumb_inactive_color">#009688</color>
<color name="container_fastscroll_thumb_active_color">#009688</color>
<!-- All Apps -->
<color name="all_apps_grid_section_text_color">#009688</color>
+ <color name="all_apps_search_market_button_focused_bg_color">#DDDDDD</color>
<!-- Widgets view -->
<color name="widgets_view_fastscroll_thumb_inactive_color">#42FFFFFF</color>
diff --git a/res/values/config.xml b/res/values/config.xml
index 73de794..6a67840 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -2,8 +2,6 @@
<!-- Dynamic Grid -->
<!-- Out of 100, the percent of space the overview bar should try and take vertically. -->
<integer name="config_dynamic_grid_overview_icon_zone_percentage">20</integer>
- <!-- Out of 100, the percent to shrink the workspace during overview mode. -->
- <integer name="config_dynamic_grid_overview_scale_percentage">80</integer>
<!-- Miscellaneous -->
<bool name="config_largeHeap">false</bool>
@@ -19,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 -->
@@ -30,6 +27,8 @@
<!-- Out of 100, the percent to shrink the workspace during spring loaded mode. -->
<integer name="config_workspaceSpringLoadShrinkPercentage">80</integer>
+ <!-- Out of 100, the percent to shrink the workspace during overview mode. -->
+ <integer name="config_workspaceOverviewShrinkPercentage">70</integer>
<!-- Fade/zoom in/out duration & scale in a Launcher overlay transition.
Note: This should be less than the config_overlayTransitionTime as they happen together. -->
@@ -74,10 +73,6 @@
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>
-
<!-- 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 589d696..5430872 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -55,9 +55,9 @@
<!-- Notes: container_bounds_inset - quantum_panel_outer_padding -->
<dimen name="container_bounds_minus_quantum_panel_padding_inset">4dp</dimen>
- <dimen name="container_fastscroll_thumb_min_width">4dp</dimen>
- <dimen name="container_fastscroll_thumb_max_width">8dp</dimen>
- <dimen name="container_fastscroll_thumb_height">64dp</dimen>
+ <dimen name="container_fastscroll_thumb_min_width">5dp</dimen>
+ <dimen name="container_fastscroll_thumb_max_width">9dp</dimen>
+ <dimen name="container_fastscroll_thumb_height">72dp</dimen>
<dimen name="container_fastscroll_thumb_touch_inset">-24dp</dimen>
<dimen name="container_fastscroll_popup_size">72dp</dimen>
<dimen name="container_fastscroll_popup_text_size">48dp</dimen>
@@ -85,6 +85,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>
@@ -111,6 +112,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>
@@ -129,4 +132,8 @@
<dimen name="blur_size_click_shadow">4dp</dimen>
<dimen name="click_shadow_high_shift">2dp</dimen>
+<!-- Pending widget -->
+ <dimen name="pending_widget_min_padding">8dp</dimen>
+ <dimen name="pending_widget_elevation">2dp</dimen>
+
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 88f149b..fefadef 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -24,10 +24,10 @@
<!-- URI used to import old favorites. [DO NOT TRANSLATE] -->
<string name="old_launcher_provider_uri" translatable="false">content://com.android.launcher2.settings/favorites?notify=true</string>
- <!-- Permission to receive the com.android.launcher3.action.LAUNCH intent -->
+ <!-- Permission to receive the com.android.launcher3.action.LAUNCH intent. [DO NOT TRANSLATE] -->
<string name="receive_launch_broadcasts_permission" translatable="false">com.android.launcher3.permission.RECEIVE_LAUNCH_BROADCASTS</string>
- <!-- Permission to receive the com.android.launcher3.action.FIRST_LOAD_COMPLETE intent -->
+ <!-- Permission to receive the com.android.launcher3.action.FIRST_LOAD_COMPLETE intent. [DO NOT TRANSLATE] -->
<string name="receive_first_load_broadcast_permission" translatable="false">com.android.launcher3.permission.RECEIVE_FIRST_LOAD_BROADCAST</string>
<!-- Application name -->
@@ -61,6 +61,9 @@
<string name="all_apps_loading_message">Loading Apps…</string>
<!-- No-search-results text. [CHAR_LIMIT=50] -->
<string name="all_apps_no_search_results">No Apps found matching \"<xliff:g id="query" example="Android">%1$s</xliff:g>\"</string>
+ <!-- Search market text. This is a format string where the first argument is the name of the activity
+ handling the search. The format string does not need to handle both of these arguments. [CHAR_LIMIT=50] -->
+ <string name="all_apps_search_market_message">Go to <xliff:g id="query" example="Play Store">%1$s</xliff:g></string>
<!-- Drag and drop -->
<skip />
diff --git a/res/xml/default_workspace_4x4.xml b/res/xml/default_workspace_4x4.xml
index 9bec86a..060a1f8 100644
--- a/res/xml/default_workspace_4x4.xml
+++ b/res/xml/default_workspace_4x4.xml
@@ -15,102 +15,33 @@
-->
<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
- <!-- Far-left screen [0] -->
- <!-- Left screen [1] -->
- <appwidget
- launcher:packageName="com.android.settings"
- launcher:className="com.android.settings.widget.SettingsAppWidgetProvider"
- launcher:screen="1"
- launcher:x="0"
- launcher:y="3"
- launcher:spanX="4"
- launcher:spanY="1" />
+ <!-- Hotseat -->
+ <include launcher:workspace="@xml/dw_phone_hotseat" />
- <!-- Middle screen [2] -->
- <appwidget
- launcher:packageName="com.android.deskclock"
- launcher:className="com.android.alarmclock.AnalogAppWidgetProvider"
- launcher:screen="2"
- launcher:x="1"
- launcher:y="0"
- launcher:spanX="2"
- launcher:spanY="2" />
- <favorite
- launcher:packageName="com.android.camera"
- launcher:className="com.android.camera.Camera"
- launcher:screen="2"
- launcher:x="0"
- launcher:y="3" />
-
- <!-- Right screen [3] -->
- <favorite
- launcher:packageName="com.android.gallery3d"
- launcher:className="com.android.gallery3d.app.Gallery"
- launcher:screen="3"
- launcher:x="1"
- launcher:y="3" />
- <favorite
- launcher:packageName="com.android.settings"
- launcher:className="com.android.settings.Settings"
- launcher:screen="3"
- launcher:x="2"
- launcher:y="3" />
-
- <!-- Far-right screen [4] -->
-
- <!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
- <!-- Dialer, Contacts, [All Apps], Messaging, Browser -->
+ <!-- Bottom row -->
<resolve
- launcher:container="-101"
launcher:screen="0"
launcher:x="0"
- launcher:y="0" >
- <favorite launcher:uri="#Intent;action=android.intent.action.DIAL;end" />
- <favorite launcher:uri="tel:123" />
- <favorite launcher:uri="#Intent;action=android.intent.action.CALL_BUTTON;end" />
-
- <favorite
- launcher:packageName="com.android.dialer"
- launcher:className="com.android.dialer.DialtactsActivity" />
+ launcher:y="3" >
+ <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_EMAIL;end" />
+ <favorite launcher:uri="mailto:" />
</resolve>
- <favorite
- launcher:packageName="com.android.contacts"
- launcher:className="com.android.contacts.activities.PeopleActivity"
- launcher:container="-101"
- launcher:screen="1"
+ <resolve
+ launcher:screen="0"
launcher:x="1"
- launcher:y="0" />
-
- <resolve
- launcher:container="-101"
- launcher:screen="3"
- launcher:x="3"
- launcher:y="0" >
- <favorite
- launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MESSAGING;end" />
- <favorite launcher:uri="sms:" />
- <favorite launcher:uri="smsto:" />
- <favorite launcher:uri="mms:" />
- <favorite launcher:uri="mmsto:" />
-
- <favorite
- launcher:packageName="com.android.mms"
- launcher:className="com.android.mms.ui.ConversationList" />
+ launcher:y="3" >
+ <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_GALLERY;end" />
+ <favorite launcher:uri="#Intent;type=images/*;end" />
</resolve>
- <resolve
- launcher:container="-101"
- launcher:screen="4"
- launcher:x="4"
- launcher:y="0" >
- <favorite
- launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_BROWSER;end" />
- <favorite launcher:uri="http://www.example.com/" />
- <favorite
- launcher:packageName="com.android.browser"
- launcher:className="com.android.browser.BrowserActivity" />
+ <resolve
+ launcher:screen="0"
+ launcher:x="3"
+ launcher:y="3" >
+ <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MARKET;end" />
+ <favorite launcher:uri="market://details?id=com.android.launcher" />
</resolve>
</favorites>
diff --git a/res/xml/default_workspace_5x5.xml b/res/xml/default_workspace_5x5.xml
index 9bec86a..3226617 100644
--- a/res/xml/default_workspace_5x5.xml
+++ b/res/xml/default_workspace_5x5.xml
@@ -15,102 +15,34 @@
-->
<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
- <!-- Far-left screen [0] -->
- <!-- Left screen [1] -->
- <appwidget
- launcher:packageName="com.android.settings"
- launcher:className="com.android.settings.widget.SettingsAppWidgetProvider"
- launcher:screen="1"
- launcher:x="0"
- launcher:y="3"
- launcher:spanX="4"
- launcher:spanY="1" />
+ <!-- Hotseat -->
+ <include launcher:workspace="@xml/dw_phone_hotseat" />
- <!-- Middle screen [2] -->
- <appwidget
- launcher:packageName="com.android.deskclock"
- launcher:className="com.android.alarmclock.AnalogAppWidgetProvider"
- launcher:screen="2"
- launcher:x="1"
- launcher:y="0"
- launcher:spanX="2"
- launcher:spanY="2" />
- <favorite
- launcher:packageName="com.android.camera"
- launcher:className="com.android.camera.Camera"
- launcher:screen="2"
- launcher:x="0"
- launcher:y="3" />
-
- <!-- Right screen [3] -->
- <favorite
- launcher:packageName="com.android.gallery3d"
- launcher:className="com.android.gallery3d.app.Gallery"
- launcher:screen="3"
- launcher:x="1"
- launcher:y="3" />
- <favorite
- launcher:packageName="com.android.settings"
- launcher:className="com.android.settings.Settings"
- launcher:screen="3"
- launcher:x="2"
- launcher:y="3" />
-
- <!-- Far-right screen [4] -->
-
- <!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
- <!-- Dialer, Contacts, [All Apps], Messaging, Browser -->
+ <!-- Bottom row -->
<resolve
- launcher:container="-101"
launcher:screen="0"
launcher:x="0"
- launcher:y="0" >
- <favorite launcher:uri="#Intent;action=android.intent.action.DIAL;end" />
- <favorite launcher:uri="tel:123" />
- <favorite launcher:uri="#Intent;action=android.intent.action.CALL_BUTTON;end" />
+ launcher:y="4" >
+ <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_EMAIL;end" />
+ <favorite launcher:uri="mailto:" />
- <favorite
- launcher:packageName="com.android.dialer"
- launcher:className="com.android.dialer.DialtactsActivity" />
</resolve>
- <favorite
- launcher:packageName="com.android.contacts"
- launcher:className="com.android.contacts.activities.PeopleActivity"
- launcher:container="-101"
- launcher:screen="1"
+ <resolve
+ launcher:screen="0"
launcher:x="1"
- launcher:y="0" />
+ launcher:y="4" >
+ <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_GALLERY;end" />
+ <favorite launcher:uri="#Intent;type=images/*;end" />
- <resolve
- launcher:container="-101"
- launcher:screen="3"
- launcher:x="3"
- launcher:y="0" >
- <favorite
- launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MESSAGING;end" />
- <favorite launcher:uri="sms:" />
- <favorite launcher:uri="smsto:" />
- <favorite launcher:uri="mms:" />
- <favorite launcher:uri="mmsto:" />
-
- <favorite
- launcher:packageName="com.android.mms"
- launcher:className="com.android.mms.ui.ConversationList" />
</resolve>
+
<resolve
- launcher:container="-101"
- launcher:screen="4"
+ launcher:screen="0"
launcher:x="4"
- launcher:y="0" >
- <favorite
- launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_BROWSER;end" />
- <favorite launcher:uri="http://www.example.com/" />
-
- <favorite
- launcher:packageName="com.android.browser"
- launcher:className="com.android.browser.BrowserActivity" />
+ launcher:y="4" >
+ <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MARKET;end" />
+ <favorite launcher:uri="market://details?id=com.android.launcher" />
</resolve>
-
</favorites>
diff --git a/res/xml/default_workspace_5x6.xml b/res/xml/default_workspace_5x6.xml
index d42a93a..bc236fb 100644
--- a/res/xml/default_workspace_5x6.xml
+++ b/res/xml/default_workspace_5x6.xml
@@ -15,101 +15,23 @@
-->
<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
- <!-- Far-left screen [0] -->
- <!-- Left screen [1] -->
- <appwidget
- launcher:packageName="com.android.settings"
- launcher:className="com.android.settings.widget.SettingsAppWidgetProvider"
- launcher:screen="1"
+ <!-- Hotseat -->
+ <include launcher:workspace="@xml/dw_tablet_hotseat" />
+
+ <!-- Bottom row -->
+ <favorite
+ launcher:screen="0"
launcher:x="0"
- launcher:y="3"
- launcher:spanX="4"
- launcher:spanY="1" />
-
- <!-- Middle screen [2] -->
- <appwidget
- launcher:packageName="com.android.deskclock"
- launcher:className="com.android.alarmclock.AnalogAppWidgetProvider"
- launcher:screen="2"
- launcher:x="1"
- launcher:y="0"
- launcher:spanX="2"
- launcher:spanY="2" />
- <favorite
- launcher:packageName="com.android.camera"
- launcher:className="com.android.camera.Camera"
- launcher:screen="2"
- launcher:x="0"
- launcher:y="3" />
-
- <!-- Right screen [3] -->
- <favorite
- launcher:packageName="com.android.gallery3d"
- launcher:className="com.android.gallery3d.app.Gallery"
- launcher:screen="3"
- launcher:x="1"
- launcher:y="3" />
- <favorite
- launcher:packageName="com.android.settings"
- launcher:className="com.android.settings.Settings"
- launcher:screen="3"
- launcher:x="2"
- launcher:y="3" />
-
- <!-- Far-right screen [4] -->
-
- <!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
- <!-- Dialer, Contacts, [All Apps], Messaging, Browser -->
- <resolve
- launcher:container="-101"
- launcher:screen="1"
- launcher:x="1"
- launcher:y="0" >
- <favorite launcher:uri="#Intent;action=android.intent.action.DIAL;end" />
- <favorite launcher:uri="tel:123" />
- <favorite launcher:uri="#Intent;action=android.intent.action.CALL_BUTTON;end" />
-
- <favorite
- launcher:packageName="com.android.dialer"
- launcher:className="com.android.dialer.DialtactsActivity" />
- </resolve>
-
- <favorite
- launcher:packageName="com.android.contacts"
- launcher:className="com.android.contacts.activities.PeopleActivity"
- launcher:container="-101"
- launcher:screen="2"
- launcher:x="2"
- launcher:y="0" />
+ launcher:y="4"
+ launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_CONTACTS;end" />
<resolve
- launcher:container="-101"
- launcher:screen="4"
- launcher:x="4"
- launcher:y="0" >
- <favorite
- launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MESSAGING;end" />
- <favorite launcher:uri="sms:" />
- <favorite launcher:uri="smsto:" />
- <favorite launcher:uri="mms:" />
- <favorite launcher:uri="mmsto:" />
-
- <favorite
- launcher:packageName="com.android.mms"
- launcher:className="com.android.mms.ui.ConversationList" />
- </resolve>
- <resolve
- launcher:container="-101"
- launcher:screen="5"
+ launcher:screen="0"
launcher:x="5"
- launcher:y="0" >
- <favorite
- launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_BROWSER;end" />
- <favorite launcher:uri="http://www.example.com/" />
-
- <favorite
- launcher:packageName="com.android.browser"
- launcher:className="com.android.browser.BrowserActivity" />
+ launcher:y="4" >
+ <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MARKET;end" />
+ <favorite launcher:uri="market://details?id=com.android.launcher" />
</resolve>
+
</favorites>
diff --git a/res/xml/dw_phone_hotseat.xml b/res/xml/dw_phone_hotseat.xml
new file mode 100644
index 0000000..b58994d
--- /dev/null
+++ b/res/xml/dw_phone_hotseat.xml
@@ -0,0 +1,63 @@
+<?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.
+-->
+
+<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
+ <!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
+ <!-- Dialer, Messaging, [All Apps], Browser, Camera -->
+ <resolve
+ launcher:container="-101"
+ launcher:screen="0"
+ launcher:x="0"
+ launcher:y="0" >
+ <favorite launcher:uri="#Intent;action=android.intent.action.DIAL;end" />
+ <favorite launcher:uri="tel:123" />
+ <favorite launcher:uri="#Intent;action=android.intent.action.CALL_BUTTON;end" />
+ </resolve>
+
+ <resolve
+ launcher:container="-101"
+ launcher:screen="1"
+ launcher:x="1"
+ launcher:y="0" >
+ <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MESSAGING;end" />
+ <favorite launcher:uri="sms:" />
+ <favorite launcher:uri="smsto:" />
+ <favorite launcher:uri="mms:" />
+ <favorite launcher:uri="mmsto:" />
+ </resolve>
+
+ <!-- All Apps -->
+
+ <resolve
+ launcher:container="-101"
+ launcher:screen="3"
+ launcher:x="3"
+ launcher:y="0" >
+ <favorite
+ launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_BROWSER;end" />
+ <favorite launcher:uri="http://www.example.com/" />
+ </resolve>
+
+ <resolve
+ launcher:container="-101"
+ launcher:screen="4"
+ launcher:x="4"
+ launcher:y="0" >
+ <favorite launcher:uri="#Intent;action=android.media.action.STILL_IMAGE_CAMERA;end" />
+ <favorite launcher:uri="#Intent;action=android.intent.action.CAMERA_BUTTON;end" />
+ </resolve>
+
+</favorites>
diff --git a/res/xml/dw_tablet_hotseat.xml b/res/xml/dw_tablet_hotseat.xml
new file mode 100644
index 0000000..671ccba
--- /dev/null
+++ b/res/xml/dw_tablet_hotseat.xml
@@ -0,0 +1,78 @@
+<?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.
+-->
+
+<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
+ <!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
+ <!-- Messaging, Email, Browser, [All Apps], Music, Gallery, Camera -->
+ <resolve
+ launcher:container="-101"
+ launcher:screen="0"
+ launcher:x="0"
+ launcher:y="0" >
+ <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MESSAGING;end" />
+ <favorite launcher:uri="sms:" />
+ <favorite launcher:uri="smsto:" />
+ <favorite launcher:uri="mms:" />
+ <favorite launcher:uri="mmsto:" />
+ </resolve>
+
+ <resolve
+ launcher:container="-101"
+ launcher:screen="1"
+ launcher:x="1"
+ launcher:y="0" >
+ <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_EMAIL;end" />
+ <favorite launcher:uri="mailto:" />
+ </resolve>
+
+ <resolve
+ launcher:container="-101"
+ launcher:screen="2"
+ launcher:x="2"
+ launcher:y="0" >
+ <favorite
+ launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_BROWSER;end" />
+ <favorite launcher:uri="http://www.example.com/" />
+ </resolve>
+
+ <!-- All Apps -->
+
+ <favorite
+ launcher:container="-101"
+ launcher:screen="4"
+ launcher:x="4"
+ launcher:y="0"
+ launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MUSIC;end" />
+
+ <resolve
+ launcher:container="-101"
+ launcher:screen="5"
+ launcher:x="5"
+ launcher:y="0" >
+ <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_GALLERY;end" />
+ <favorite launcher:uri="#Intent;type=images/*;end" />
+ </resolve>
+
+ <resolve
+ launcher:container="-101"
+ launcher:screen="6"
+ launcher:x="6"
+ launcher:y="0" >
+ <favorite launcher:uri="#Intent;action=android.media.action.STILL_IMAGE_CAMERA;end" />
+ <favorite launcher:uri="#Intent;action=android.intent.action.CAMERA_BUTTON;end" />
+ </resolve>
+
+</favorites>
diff --git a/src/com/android/launcher3/AnotherWindowDropTarget.java b/src/com/android/launcher3/AnotherWindowDropTarget.java
new file mode 100644
index 0000000..eaac6be
--- /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/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index ea7c221..6818929 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.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 @@
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;
@@ -75,8 +81,8 @@
mResizeMode = info.resizeMode;
mDragLayer = dragLayer;
- mMinHSpan = info.getMinSpanX(mLauncher);
- mMinVSpan = info.getMinSpanY(mLauncher);
+ mMinHSpan = info.minSpanX;
+ mMinVSpan = info.minSpanY;
setBackgroundResource(R.drawable.widget_resize_shadow);
setForeground(getResources().getDrawable(R.drawable.widget_resize_frame));
@@ -341,28 +347,27 @@
}
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 @@
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 5e7a012..10fdd87 100644
--- a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
+++ b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
@@ -48,7 +48,8 @@
final AppWidgetProviderInfo provider = widgets.getAppWidgetInfo(newWidgetIds[i]);
final int state;
if (LauncherModel.isValidProvider(provider)) {
- state = LauncherAppWidgetInfo.RESTORE_COMPLETED;
+ // This will ensure that we show 'Click to setup' UI if required.
+ state = LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
} else {
state = LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
}
diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java
index 99a98dd..440e4e7 100644
--- a/src/com/android/launcher3/AutoInstallsLayout.java
+++ b/src/com/android/launcher3/AutoInstallsLayout.java
@@ -355,7 +355,7 @@
return addShortcut(info.loadLabel(mPackageManager).toString(),
intent, Favorites.ITEM_TYPE_APPLICATION);
} catch (PackageManager.NameNotFoundException e) {
- if (LOGD) Log.w(TAG, "Unable to add favorite: " + packageName + "/" + className, e);
+ Log.e(TAG, "Unable to add favorite: " + packageName + "/" + className, e);
}
return -1;
} else {
@@ -367,7 +367,7 @@
* Helper method to allow extending the parser capabilities
*/
protected long invalidPackageOrClass(XmlResourceParser parser) {
- if (LOGD) Log.d(TAG, "Skipping invalid <favorite> with no component");
+ Log.w(TAG, "Skipping invalid <favorite> with no component");
return -1;
}
}
diff --git a/src/com/android/launcher3/BaseContainerView.java b/src/com/android/launcher3/BaseContainerView.java
index c8de9df..e0946ea 100644
--- a/src/com/android/launcher3/BaseContainerView.java
+++ b/src/com/android/launcher3/BaseContainerView.java
@@ -22,6 +22,8 @@
import android.util.Log;
import android.widget.LinearLayout;
+import com.android.launcher3.config.ProviderConfig;
+
/**
* A base container view, which supports resizing.
*/
@@ -34,10 +36,12 @@
// The bounds of the search bar. Only the left, top, right are used to inset the
// search bar and the height is determined by the measurement of the layout
private Rect mFixedSearchBarBounds = new Rect();
- // The bounds of the container
+ // The computed bounds of the search bar
+ private Rect mSearchBarBounds = new Rect();
+ // The computed bounds of the container
protected Rect mContentBounds = new Rect();
- // The padding to apply to the container to achieve the bounds
- protected Rect mContentPadding = new Rect();
+ // The computed padding to apply to the container to achieve the container bounds
+ private Rect mContentPadding = new Rect();
// The inset to apply to the edges and between the search bar and the container
private int mContainerBoundsInset;
private boolean mHasSearchBar;
@@ -69,7 +73,7 @@
* 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);
}
@@ -90,7 +94,7 @@
*/
protected void updateBackgroundAndPaddings() {
Rect padding;
- Rect searchBarBounds = new Rect(mFixedSearchBarBounds);
+ Rect searchBarBounds = new Rect();
if (!isValidSearchBarBounds(mFixedSearchBarBounds)) {
// Use the default bounds
padding = new Rect(mInsets.left + mContainerBoundsInset,
@@ -110,14 +114,20 @@
(mHasSearchBar ? 0 : (mInsets.top + mContainerBoundsInset)),
getMeasuredWidth() - mFixedSearchBarBounds.right,
mInsets.bottom + mContainerBoundsInset);
+
+ // Use the search bounds
+ searchBarBounds.set(mFixedSearchBarBounds);
}
- if (!padding.equals(mContentPadding) || !searchBarBounds.equals(mFixedSearchBarBounds)) {
+
+ // If either the computed container padding has changed, or the computed search bar bounds
+ // has changed, then notify the container
+ if (!padding.equals(mContentPadding) || !searchBarBounds.equals(mSearchBarBounds)) {
mContentPadding.set(padding);
mContentBounds.set(padding.left, padding.top,
getMeasuredWidth() - padding.right,
getMeasuredHeight() - padding.bottom);
- mFixedSearchBarBounds.set(searchBarBounds);
- onUpdateBackgroundAndPaddings(mFixedSearchBarBounds, padding);
+ mSearchBarBounds.set(searchBarBounds);
+ onUpdateBackgroundAndPaddings(mSearchBarBounds, padding);
}
}
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java
index 0fae427..f0d8b3b 100644
--- a/src/com/android/launcher3/BaseRecyclerView.java
+++ b/src/com/android/launcher3/BaseRecyclerView.java
@@ -92,9 +92,15 @@
// TODO(winsonc): If we want to animate the section heads while scrolling, we can
// initiate that here if the recycler view scroll state is not
// RecyclerView.SCROLL_STATE_IDLE.
+
+ onUpdateScrollbar(dy);
}
}
+ public void reset() {
+ mScrollbar.reattachThumbToScroll();
+ }
+
@Override
protected void onFinishInflate() {
super.onFinishInflate();
@@ -143,7 +149,7 @@
mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY);
break;
}
- return mScrollbar.isDragging();
+ return mScrollbar.isDraggingThumb();
}
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
@@ -185,12 +191,10 @@
* AvailableScrollHeight = Total height of the all items - last page height
*
* This assumes that all rows are the same height.
- *
- * @param yOffset the offset from the top of the recycler view to start tracking.
*/
- protected int getAvailableScrollHeight(int rowCount, int rowHeight, int yOffset) {
+ protected int getAvailableScrollHeight(int rowCount, int rowHeight) {
int visibleHeight = getHeight() - mBackgroundPadding.top - mBackgroundPadding.bottom;
- int scrollHeight = getPaddingTop() + yOffset + rowCount * rowHeight + getPaddingBottom();
+ int scrollHeight = getPaddingTop() + rowCount * rowHeight + getPaddingBottom();
int availableScrollHeight = scrollHeight - visibleHeight;
return availableScrollHeight;
}
@@ -222,7 +226,7 @@
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
- onUpdateScrollbar();
+ onUpdateScrollbar(0);
mScrollbar.draw(canvas);
}
@@ -234,24 +238,21 @@
* @param scrollPosState the current scroll position
* @param rowCount the number of rows, used to calculate the total scroll height (assumes that
* all rows are the same height)
- * @param yOffset the offset to start tracking in the recycler view (only used for all apps)
*/
protected void synchronizeScrollBarThumbOffsetToViewScroll(ScrollPositionState scrollPosState,
- int rowCount, int yOffset) {
- int availableScrollHeight = getAvailableScrollHeight(rowCount, scrollPosState.rowHeight,
- yOffset);
- int availableScrollBarHeight = getAvailableScrollBarHeight();
-
+ int rowCount) {
// Only show the scrollbar if there is height to be scrolled
+ int availableScrollBarHeight = getAvailableScrollBarHeight();
+ int availableScrollHeight = getAvailableScrollHeight(rowCount, scrollPosState.rowHeight);
if (availableScrollHeight <= 0) {
- mScrollbar.setScrollbarThumbOffset(-1, -1);
+ mScrollbar.setThumbOffset(-1, -1);
return;
}
// Calculate the current scroll position, the scrollY of the recycler view accounts for the
// view padding, while the scrollBarY is drawn right up to the background padding (ignoring
// padding)
- int scrollY = getPaddingTop() + yOffset +
+ int scrollY = getPaddingTop() +
(scrollPosState.rowIndex * scrollPosState.rowHeight) - scrollPosState.rowTopOffset;
int scrollBarY = mBackgroundPadding.top +
(int) (((float) scrollY / availableScrollHeight) * availableScrollBarHeight);
@@ -261,9 +262,9 @@
if (Utilities.isRtl(getResources())) {
scrollBarX = mBackgroundPadding.left;
} else {
- scrollBarX = getWidth() - mBackgroundPadding.right - mScrollbar.getWidth();
+ scrollBarX = getWidth() - mBackgroundPadding.right - mScrollbar.getThumbWidth();
}
- mScrollbar.setScrollbarThumbOffset(scrollBarX, scrollBarY);
+ mScrollbar.setThumbOffset(scrollBarX, scrollBarY);
}
/**
@@ -276,10 +277,15 @@
* Updates the bounds for the scrollbar.
* <p>Override in each subclass of this base class.
*/
- public abstract void onUpdateScrollbar();
+ public abstract void onUpdateScrollbar(int dy);
/**
* <p>Override in each subclass of this base class.
*/
public void onFastScrollCompleted() {}
+
+ /**
+ * Returns information about the item that the recycler view is currently scrolled to.
+ */
+ protected abstract void getCurScrollState(ScrollPositionState stateOut);
}
\ No newline at end of file
diff --git a/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java b/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
index 2c4184d..f76aed7 100644
--- a/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
+++ b/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
@@ -23,6 +23,7 @@
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
+import android.graphics.Path;
import android.graphics.Point;
import android.graphics.Rect;
import android.view.MotionEvent;
@@ -51,14 +52,20 @@
private int mThumbActiveColor;
@Thunk Point mThumbOffset = new Point(-1, -1);
@Thunk Paint mThumbPaint;
- private Paint mTrackPaint;
private int mThumbMinWidth;
private int mThumbMaxWidth;
@Thunk int mThumbWidth;
@Thunk int mThumbHeight;
+ private int mThumbCurvature;
+ private Path mThumbPath = new Path();
+ private Paint mTrackPaint;
+ private int mTrackWidth;
+ private float mLastTouchY;
// The inset is the buffer around which a point will still register as a click on the scrollbar
private int mTouchInset;
private boolean mIsDragging;
+ private boolean mIsThumbDetached;
+ private boolean mCanThumbDetach;
// This is the offset from the top of the scrollbar when the user first starts touching. To
// prevent jumping, this offset is applied as the user scrolls.
@@ -72,51 +79,74 @@
mPopup = new BaseRecyclerViewFastScrollPopup(rv, res);
mTrackPaint = new Paint();
mTrackPaint.setColor(rv.getFastScrollerTrackColor(Color.BLACK));
- mTrackPaint.setAlpha(0);
+ mTrackPaint.setAlpha(MAX_TRACK_ALPHA);
mThumbInactiveColor = rv.getFastScrollerThumbInactiveColor(
res.getColor(R.color.container_fastscroll_thumb_inactive_color));
mThumbActiveColor = res.getColor(R.color.container_fastscroll_thumb_active_color);
mThumbPaint = new Paint();
+ mThumbPaint.setAntiAlias(true);
mThumbPaint.setColor(mThumbInactiveColor);
+ mThumbPaint.setStyle(Paint.Style.FILL);
mThumbWidth = mThumbMinWidth = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_min_width);
mThumbMaxWidth = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_max_width);
mThumbHeight = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_height);
+ mThumbCurvature = mThumbMaxWidth - mThumbMinWidth;
mTouchInset = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_touch_inset);
}
- public void setScrollbarThumbOffset(int x, int y) {
+ public void setDetachThumbOnFastScroll() {
+ mCanThumbDetach = true;
+ }
+
+ public void reattachThumbToScroll() {
+ mIsThumbDetached = false;
+ }
+
+ public void setThumbOffset(int x, int y) {
if (mThumbOffset.x == x && mThumbOffset.y == y) {
return;
}
- mInvalidateRect.set(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, mRv.getHeight());
+ mInvalidateRect.set(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
+ mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
mThumbOffset.set(x, y);
- mInvalidateRect.union(new Rect(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth,
- mRv.getHeight()));
+ updateThumbPath();
+ mInvalidateRect.union(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
+ mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
mRv.invalidate(mInvalidateRect);
}
- // Setter/getter for the search bar width for animations
- public void setWidth(int width) {
- mInvalidateRect.set(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, mRv.getHeight());
+ public Point getThumbOffset() {
+ return mThumbOffset;
+ }
+
+ // Setter/getter for the thumb bar width for animations
+ public void setThumbWidth(int width) {
+ mInvalidateRect.set(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
+ mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
mThumbWidth = width;
- mInvalidateRect.union(new Rect(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth,
- mRv.getHeight()));
+ updateThumbPath();
+ mInvalidateRect.union(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
+ mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
mRv.invalidate(mInvalidateRect);
}
- public int getWidth() {
+ public int getThumbWidth() {
return mThumbWidth;
}
- // Setter/getter for the track background alpha for animations
- public void setTrackAlpha(int alpha) {
- mTrackPaint.setAlpha(alpha);
- mInvalidateRect.set(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, mRv.getHeight());
+ // Setter/getter for the track bar width for animations
+ public void setTrackWidth(int width) {
+ mInvalidateRect.set(mThumbOffset.x - mThumbCurvature, 0, mThumbOffset.x + mThumbWidth,
+ mRv.getHeight());
+ mTrackWidth = width;
+ updateThumbPath();
+ mInvalidateRect.union(mThumbOffset.x - mThumbCurvature, 0, mThumbOffset.x + mThumbWidth,
+ mRv.getHeight());
mRv.invalidate(mInvalidateRect);
}
- public int getTrackAlpha() {
- return mTrackPaint.getAlpha();
+ public int getTrackWidth() {
+ return mTrackWidth;
}
public int getThumbHeight() {
@@ -127,10 +157,18 @@
return mThumbMaxWidth;
}
- public boolean isDragging() {
+ public float getLastTouchY() {
+ return mLastTouchY;
+ }
+
+ public boolean isDraggingThumb() {
return mIsDragging;
}
+ public boolean isThumbDetached() {
+ return mIsThumbDetached;
+ }
+
/**
* Handles the touch event and determines whether to show the fast scroller (or updates it if
* it is already showing).
@@ -152,6 +190,9 @@
Math.abs(y - downY) > config.getScaledTouchSlop()) {
mRv.getParent().requestDisallowInterceptTouchEvent(true);
mIsDragging = true;
+ if (mCanThumbDetach) {
+ mIsThumbDetached = true;
+ }
mTouchOffset += (lastY - downY);
mPopup.animateVisibility(true);
animateScrollbar(true);
@@ -166,11 +207,13 @@
mPopup.setSectionName(sectionName);
mPopup.animateVisibility(!sectionName.isEmpty());
mRv.invalidate(mPopup.updateFastScrollerBounds(mRv, lastY));
+ mLastTouchY = boundedY;
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mTouchOffset = 0;
+ mLastTouchY = 0;
if (mIsDragging) {
mIsDragging = false;
mPopup.animateVisibility(false);
@@ -189,8 +232,7 @@
if (mTrackPaint.getAlpha() > 0) {
canvas.drawRect(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, mRv.getHeight(), mTrackPaint);
}
- canvas.drawRect(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
- mThumbOffset.y + mThumbHeight, mThumbPaint);
+ canvas.drawPath(mThumbPath, mThumbPaint);
// Draw the popup
mPopup.draw(canvas);
@@ -203,27 +245,46 @@
if (mScrollbarAnimator != null) {
mScrollbarAnimator.cancel();
}
- ObjectAnimator trackAlphaAnim = ObjectAnimator.ofInt(this, "trackAlpha",
- isScrolling ? MAX_TRACK_ALPHA : 0);
- ObjectAnimator thumbWidthAnim = ObjectAnimator.ofInt(this, "width",
- isScrolling ? mThumbMaxWidth : mThumbMinWidth);
- ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(),
- mThumbPaint.getColor(), isScrolling ? mThumbActiveColor : mThumbInactiveColor);
- colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animator) {
- mThumbPaint.setColor((Integer) animator.getAnimatedValue());
- mRv.invalidate(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
- mThumbOffset.y + mThumbHeight);
- }
- });
+
mScrollbarAnimator = new AnimatorSet();
- mScrollbarAnimator.playTogether(trackAlphaAnim, thumbWidthAnim, colorAnimation);
+ ObjectAnimator trackWidthAnim = ObjectAnimator.ofInt(this, "trackWidth",
+ isScrolling ? mThumbMaxWidth : mThumbMinWidth);
+ ObjectAnimator thumbWidthAnim = ObjectAnimator.ofInt(this, "thumbWidth",
+ isScrolling ? mThumbMaxWidth : mThumbMinWidth);
+ mScrollbarAnimator.playTogether(trackWidthAnim, thumbWidthAnim);
+ if (mThumbActiveColor != mThumbInactiveColor) {
+ ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(),
+ mThumbPaint.getColor(), isScrolling ? mThumbActiveColor : mThumbInactiveColor);
+ colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animator) {
+ mThumbPaint.setColor((Integer) animator.getAnimatedValue());
+ mRv.invalidate(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
+ mThumbOffset.y + mThumbHeight);
+ }
+ });
+ mScrollbarAnimator.play(colorAnimation);
+ }
mScrollbarAnimator.setDuration(SCROLL_BAR_VIS_DURATION);
mScrollbarAnimator.start();
}
/**
+ * Updates the path for the thumb drawable.
+ */
+ private void updateThumbPath() {
+ mThumbCurvature = mThumbMaxWidth - mThumbWidth;
+ mThumbPath.reset();
+ mThumbPath.moveTo(mThumbOffset.x + mThumbWidth, mThumbOffset.y); // tr
+ mThumbPath.lineTo(mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight); // br
+ mThumbPath.lineTo(mThumbOffset.x, mThumbOffset.y + mThumbHeight); // bl
+ mThumbPath.cubicTo(mThumbOffset.x, mThumbOffset.y + mThumbHeight,
+ mThumbOffset.x - mThumbCurvature, mThumbOffset.y + mThumbHeight / 2,
+ mThumbOffset.x, mThumbOffset.y); // bl2tl
+ mThumbPath.close();
+ }
+
+ /**
* Returns whether the specified points are near the scroll bar bounds.
*/
private boolean isNearPoint(int x, int y) {
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index a0be8ea..5070878 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -34,11 +34,13 @@
import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.MotionEvent;
+import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewParent;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.widget.TextView;
+
import com.android.launcher3.IconCache.IconLoadRequest;
import com.android.launcher3.model.PackageItemInfo;
@@ -508,7 +510,7 @@
mIcon.setBounds(0, 0, iconSize, iconSize);
}
if (mLayoutHorizontal) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ if (Utilities.ATLEAST_JB_MR1) {
setCompoundDrawablesRelative(mIcon, null, null, null);
} else {
setCompoundDrawables(mIcon, null, null, null);
@@ -538,6 +540,13 @@
} else if (info instanceof ShortcutInfo) {
applyFromShortcutInfo((ShortcutInfo) info,
LauncherAppState.getInstance().getIconCache());
+ if ((info.rank < FolderIcon.NUM_ITEMS_IN_PREVIEW) && (info.container >= 0)) {
+ View folderIcon =
+ mLauncher.getWorkspace().getHomescreenIconByItemId(info.container);
+ if (folderIcon != null) {
+ folderIcon.invalidate();
+ }
+ }
} else if (info instanceof PackageItemInfo) {
applyFromPackageItemInfo((PackageItemInfo) info);
}
diff --git a/src/com/android/launcher3/BuildInfo.java b/src/com/android/launcher3/BuildInfo.java
index b49ee0d..1392d7a 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 574f229..dc29f7d 100644
--- a/src/com/android/launcher3/ButtonDropTarget.java
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -38,6 +38,9 @@
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;
/**
@@ -92,7 +95,7 @@
// drawableLeft and drawableStart.
mDrawable = getResources().getDrawable(resId);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ if (Utilities.ATLEAST_JB_MR1) {
setCompoundDrawablesRelativeWithIntrinsicBounds(mDrawable, null, null, null);
} else {
setCompoundDrawablesWithIntrinsicBounds(mDrawable, null, null, null);
@@ -113,7 +116,7 @@
@Override
public final void onDragEnter(DragObject d) {
d.dragView.setColor(mHoverColor);
- if (Utilities.isLmpOrAbove()) {
+ if (Utilities.ATLEAST_LOLLIPOP) {
animateTextColor(mHoverColor);
} else {
if (mCurrentFilter == null) {
@@ -130,8 +133,8 @@
// Do nothing
}
- protected void resetHoverColor() {
- if (Utilities.isLmpOrAbove()) {
+ protected void resetHoverColor() {
+ if (Utilities.ATLEAST_LOLLIPOP) {
animateTextColor(mOriginalTextColor.getDefaultColor());
} else {
mDrawable.setColorFilter(null);
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index e96768f..772bb7b 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -18,7 +18,6 @@
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;
@@ -54,8 +53,8 @@
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.Thunk;
-import com.android.launcher3.widget.PendingAddWidgetInfo;
import java.util.ArrayList;
import java.util.Arrays;
@@ -212,6 +211,7 @@
mBackground = (TransitionDrawable) res.getDrawable(R.drawable.bg_screenpanel);
mBackground.setCallback(this);
+ mBackground.setAlpha((int) (mBackgroundAlpha * 255));
mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE *
grid.iconSizePx);
@@ -405,7 +405,7 @@
mIsDragTarget = false;
}
- boolean isDragTarget() {
+ public boolean isDragTarget() {
return mIsDragTarget;
}
@@ -415,13 +415,17 @@
if (mIsDragOverlapping) {
mBackground.startTransition(BACKGROUND_ACTIVATE_DURATION);
} else {
- mBackground.reverseTransition(BACKGROUND_ACTIVATE_DURATION);
+ if (mBackgroundAlpha > 0f) {
+ mBackground.reverseTransition(BACKGROUND_ACTIVATE_DURATION);
+ } else {
+ mBackground.resetTransition();
+ }
}
invalidate();
}
}
- boolean getIsDragOverlapping() {
+ public boolean getIsDragOverlapping() {
return mIsDragOverlapping;
}
@@ -444,12 +448,9 @@
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);
}
}
@@ -564,7 +565,7 @@
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.
@@ -1027,53 +1028,57 @@
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();
}
@@ -2155,17 +2160,14 @@
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();
}
}
@@ -2678,65 +2680,6 @@
resultRect.set(x, y, x + width, y + height);
}
- /**
- * Computes the required horizontal and vertical cell spans to always
- * fit the given rectangle.
- *
- * @param width Width in pixels
- * @param height Height in pixels
- * @param result An array of length 2 in which to store the result (may be null).
- */
- public static int[] rectToCell(Launcher launcher, int width, int height, int[] result) {
- return rectToCell(launcher.getDeviceProfile(), launcher, width, height, result);
- }
-
- public static int[] rectToCell(DeviceProfile grid, Context context, int width, int height,
- int[] result) {
- Rect padding = grid.getWorkspacePadding(Utilities.isRtl(context.getResources()));
-
- // Always assume we're working with the smallest span to make sure we
- // reserve enough space in both orientations.
- int parentWidth = DeviceProfile.calculateCellWidth(grid.widthPx
- - padding.left - padding.right, (int) grid.inv.numColumns);
- int parentHeight = DeviceProfile.calculateCellHeight(grid.heightPx
- - padding.top - padding.bottom, (int) grid.inv.numRows);
- int smallerSize = Math.min(parentWidth, parentHeight);
-
- // Always round up to next largest cell
- int spanX = (int) Math.ceil(width / (float) smallerSize);
- int spanY = (int) Math.ceil(height / (float) smallerSize);
-
- if (result == null) {
- return new int[] { spanX, spanY };
- }
- result[0] = spanX;
- result[1] = spanY;
- return result;
- }
-
- /**
- * Calculate the grid spans needed to fit given item
- */
- public void calculateSpans(ItemInfo info) {
- final int minWidth;
- final int minHeight;
-
- if (info instanceof LauncherAppWidgetInfo) {
- minWidth = ((LauncherAppWidgetInfo) info).minWidth;
- minHeight = ((LauncherAppWidgetInfo) info).minHeight;
- } else if (info instanceof PendingAddWidgetInfo) {
- minWidth = ((PendingAddWidgetInfo) info).minWidth;
- minHeight = ((PendingAddWidgetInfo) info).minHeight;
- } else {
- // It's not a widget, so it must be 1x1
- info.spanX = info.spanY = 1;
- return;
- }
- int[] spans = rectToCell(mLauncher, minWidth, minHeight, null);
- info.spanX = spans[0];
- info.spanY = spans[1];
- }
-
private void clearOccupiedCells() {
for (int x = 0; x < mCountX; x++) {
for (int y = 0; y < mCountY; y++) {
@@ -2859,10 +2802,10 @@
// 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;
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index ae6e3ba..32bf192 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -24,6 +24,7 @@
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;
@@ -111,7 +112,6 @@
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 5966112..6364d90 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -56,7 +56,6 @@
private final int overviewModeBarItemWidthPx;
private final int overviewModeBarSpacerWidthPx;
private final float overviewModeIconZoneRatio;
- private final float overviewModeScaleFactor;
// Workspace
private int desiredWorkspaceLeftRightMarginPx;
@@ -136,8 +135,6 @@
res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_spacer_width);
overviewModeIconZoneRatio =
res.getInteger(R.integer.config_dynamic_grid_overview_icon_zone_percentage) / 100f;
- overviewModeScaleFactor =
- res.getInteger(R.integer.config_dynamic_grid_overview_scale_percentage) / 100f;
iconDrawablePaddingOriginalPx =
res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding);
@@ -283,6 +280,18 @@
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);
@@ -334,18 +343,11 @@
}
}
- Rect getOverviewModeButtonBarRect() {
+ int getOverviewModeButtonBarHeight() {
int zoneHeight = (int) (overviewModeIconZoneRatio * availableHeightPx);
zoneHeight = Math.min(overviewModeMaxIconZoneHeightPx,
Math.max(overviewModeMinIconZoneHeightPx, zoneHeight));
- return new Rect(0, availableHeightPx - zoneHeight, 0, availableHeightPx);
- }
-
- public float getOverviewModeScale(boolean isLayoutRtl) {
- Rect workspacePadding = getWorkspacePadding(isLayoutRtl);
- Rect overviewBar = getOverviewModeButtonBarRect();
- int pageSpace = availableHeightPx - workspacePadding.top - workspacePadding.bottom;
- return (overviewModeScaleFactor * (pageSpace - overviewBar.height())) / pageSpace;
+ return zoneHeight;
}
// The rect returned will be extended to below the system ui that covers the workspace
@@ -394,7 +396,7 @@
final boolean isLayoutRtl = Utilities.isRtl(launcher.getResources());
// Layout the search bar space
- View searchBar = launcher.getSearchBar();
+ 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
@@ -476,7 +478,7 @@
// Layout the Overview Mode
ViewGroup overviewMode = launcher.getOverviewPanel();
if (overviewMode != null) {
- Rect r = getOverviewModeButtonBarRect();
+ int overviewButtonBarHeight = getOverviewModeButtonBarHeight();
lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams();
lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
@@ -485,7 +487,7 @@
int maxWidth = totalItemWidth + (visibleChildCount-1) * overviewModeBarSpacerWidthPx;
lp.width = Math.min(availableWidthPx, maxWidth);
- lp.height = r.height();
+ lp.height = overviewButtonBarHeight;
overviewMode.setLayoutParams(lp);
if (lp.width > totalItemWidth && visibleChildCount > 1) {
diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java
index 3748464..592cd32 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;
diff --git a/src/com/android/launcher3/allapps/AllAppsSearchEditView.java b/src/com/android/launcher3/ExtendedEditText.java
similarity index 67%
rename from src/com/android/launcher3/allapps/AllAppsSearchEditView.java
rename to src/com/android/launcher3/ExtendedEditText.java
index b7dcd66..bf4551b 100644
--- a/src/com/android/launcher3/allapps/AllAppsSearchEditView.java
+++ b/src/com/android/launcher3/ExtendedEditText.java
@@ -13,37 +13,38 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.launcher3.allapps;
+package com.android.launcher3;
import android.content.Context;
import android.util.AttributeSet;
+import android.view.DragEvent;
import android.view.KeyEvent;
import android.widget.EditText;
/**
- * The edit text for the search container
+ * The edit text that reports back when the back key has been pressed.
*/
-public class AllAppsSearchEditView extends EditText {
+public class ExtendedEditText extends EditText {
/**
* Implemented by listeners of the back key.
*/
public interface OnBackKeyListener {
- public void onBackKey();
+ public boolean onBackKey();
}
private OnBackKeyListener mBackKeyListener;
- public AllAppsSearchEditView(Context context) {
- this(context, null);
+ public ExtendedEditText(Context context) {
+ super(context);
}
- public AllAppsSearchEditView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
+ public ExtendedEditText(Context context, AttributeSet attrs) {
+ super(context, attrs);
}
- public AllAppsSearchEditView(Context context, AttributeSet attrs, int defStyleAttr) {
+ public ExtendedEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@@ -56,10 +57,16 @@
// If this is a back key, propagate the key back to the listener
if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
if (mBackKeyListener != null) {
- mBackKeyListener.onBackKey();
+ return mBackKeyListener.onBackKey();
}
return false;
}
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 57aec32..3751b88 100644
--- a/src/com/android/launcher3/FocusHelper.java
+++ b/src/com/android/launcher3/FocusHelper.java
@@ -22,6 +22,7 @@
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;
@@ -74,7 +75,7 @@
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 ecf93e4..2337f6e 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 @@
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 @@
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 21f23b5..afb5fd8 100644
--- a/src/com/android/launcher3/Folder.java
+++ b/src/com/android/launcher3/Folder.java
@@ -52,11 +52,13 @@
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;
@@ -124,7 +126,7 @@
@Thunk FolderPagedView mContent;
@Thunk View mContentWrapper;
- FolderEditText mFolderName;
+ ExtendedEditText mFolderName;
private View mFooter;
private int mFooterHeight;
@@ -196,8 +198,15 @@
mContent = (FolderPagedView) findViewById(R.id.folder_content);
mContent.setFolder(this);
- mFolderName = (FolderEditText) findViewById(R.id.folder_name);
- mFolderName.setFolder(this);
+ mFolderName = (ExtendedEditText) findViewById(R.id.folder_name);
+ mFolderName.setOnBackKeyListener(new ExtendedEditText.OnBackKeyListener() {
+ @Override
+ public boolean onBackKey() {
+ // Close the activity on back key press
+ doneEditingFolderName(true);
+ return false;
+ }
+ });
mFolderName.setOnFocusChangeListener(this);
// We disable action mode for now since it messes up the view on phones
@@ -275,7 +284,7 @@
@Override
public void enableAccessibleDrag(boolean enable) {
- mLauncher.getSearchBar().enableAccessibleDrag(enable);
+ mLauncher.getSearchDropTargetBar().enableAccessibleDrag(enable);
for (int i = 0; i < mContent.getChildCount(); i++) {
mContent.getPageAt(i).enableAccessibleDrag(enable, CellLayout.FOLDER_ACCESSIBILITY_DRAG);
}
@@ -446,15 +455,11 @@
Animator openFolderAnim = null;
final Runnable onCompleteRunnable;
- if (!Utilities.isLmpOrAbove()) {
+ if (!Utilities.ATLEAST_LOLLIPOP) {
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;
@@ -477,8 +482,8 @@
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);
@@ -557,11 +562,12 @@
final boolean updateAnimationFlag = !mDragInProgress;
openFolderAnim.addListener(new AnimatorListenerAdapter() {
+ @SuppressLint("InlinedApi")
@Override
public void onAnimationEnd(Animator animation) {
mFolderName.animate().setDuration(FOLDER_NAME_ANIMATION_DURATION)
.translationX(0)
- .setInterpolator(Utilities.isLmpOrAbove() ?
+ .setInterpolator(Utilities.ATLEAST_LOLLIPOP ?
AnimationUtils.loadInterpolator(mLauncher,
android.R.interpolator.fast_out_slow_in)
: new LogDecelerateInterpolator(100, 0));
@@ -623,12 +629,7 @@
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) {
@@ -1389,7 +1390,7 @@
}
// Compares item position based on rank and position giving priority to the rank.
- private static final Comparator<ItemInfo> ITEM_POS_COMPARATOR = new Comparator<ItemInfo>() {
+ public static final Comparator<ItemInfo> ITEM_POS_COMPARATOR = new Comparator<ItemInfo>() {
@Override
public int compare(ItemInfo lhs, ItemInfo rhs) {
diff --git a/src/com/android/launcher3/FolderEditText.java b/src/com/android/launcher3/FolderEditText.java
deleted file mode 100644
index c311008..0000000
--- a/src/com/android/launcher3/FolderEditText.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package com.android.launcher3;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.KeyEvent;
-import android.widget.EditText;
-
-public class FolderEditText extends EditText {
-
- private Folder mFolder;
-
- public FolderEditText(Context context) {
- super(context);
- }
-
- public FolderEditText(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public FolderEditText(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- public void setFolder(Folder folder) {
- mFolder = folder;
- }
-
- @Override
- public boolean onKeyPreIme(int keyCode, KeyEvent event) {
- // Catch the back button on the soft keyboard so that we can just close the activity
- if (event.getKeyCode() == android.view.KeyEvent.KEYCODE_BACK) {
- mFolder.doneEditingFolderName(true);
- }
- return super.onKeyPreIme(keyCode, event);
- }
-}
diff --git a/src/com/android/launcher3/FolderIcon.java b/src/com/android/launcher3/FolderIcon.java
index de0df0d..a175d86 100644
--- a/src/com/android/launcher3/FolderIcon.java
+++ b/src/com/android/launcher3/FolderIcon.java
@@ -43,6 +43,8 @@
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;
diff --git a/src/com/android/launcher3/FolderPagedView.java b/src/com/android/launcher3/FolderPagedView.java
index f2ec1b6..d8ef34e 100644
--- a/src/com/android/launcher3/FolderPagedView.java
+++ b/src/com/android/launcher3/FolderPagedView.java
@@ -30,6 +30,7 @@
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;
@@ -329,6 +330,10 @@
lp.cellY = info.cellY;
currentPage.addViewToCellLayout(
v, -1, mFolder.mLauncher.getViewIdForItem(info), lp, true);
+
+ if (rank < FolderIcon.NUM_ITEMS_IN_PREVIEW && v instanceof BubbleTextView) {
+ ((BubbleTextView) v).verifyHighRes();
+ }
}
rank ++;
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 17fdeb1..a9cff5e 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/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java
index 4bc5019..6d5bbab 100644
--- a/src/com/android/launcher3/ItemInfo.java
+++ b/src/com/android/launcher3/ItemInfo.java
@@ -134,18 +134,11 @@
}
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);
@@ -153,6 +146,27 @@
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.ITEM_TYPE);
+ cellY = values.getAsInteger(LauncherSettings.Favorites.ITEM_TYPE);
+ spanX = values.getAsInteger(LauncherSettings.Favorites.ITEM_TYPE);
+ spanY = values.getAsInteger(LauncherSettings.Favorites.ITEM_TYPE);
+ rank = values.getAsInteger(LauncherSettings.Favorites.ITEM_TYPE);
+ }
+
+ /**
+ * 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);
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 279a82b..d926cc1 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -20,7 +20,6 @@
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.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;
@@ -74,7 +74,6 @@
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;
@@ -103,6 +102,10 @@
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;
@@ -116,8 +119,6 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collection;
@@ -132,9 +133,8 @@
*/
public class Launcher extends Activity
implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks,
- View.OnTouchListener, PageSwitchListener, LauncherProviderChangeListener,
- LauncherStateTransitionAnimation.Callbacks {
- static final String TAG = "Launcher";
+ View.OnTouchListener, PageSwitchListener, LauncherProviderChangeListener {
+ public static final String TAG = "Launcher";
static final boolean LOGD = false;
static final boolean PROFILE_STARTUP = false;
@@ -177,18 +177,8 @@
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
@@ -239,8 +229,6 @@
private final BroadcastReceiver mCloseSystemDialogsReceiver
= new CloseSystemDialogsIntentReceiver();
- private LayoutInflater mInflater;
-
@Thunk Workspace mWorkspace;
private View mLauncherView;
private View mPageIndicators;
@@ -272,7 +260,6 @@
@Thunk WidgetsContainerView mWidgetsView;
@Thunk WidgetsModel mWidgetsModel;
- private boolean mAutoAdvanceRunning = false;
private AppWidgetHostView mQsb;
private Bundle mSavedState;
@@ -306,16 +293,17 @@
// 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;
@@ -357,18 +345,6 @@
}
}
- // TODO: remove this field and call method directly when Launcher3 can depend on M APIs
- private static Method sClipRevealMethod = null;
- static {
- Class<?> activityOptionsClass = ActivityOptions.class;
- try {
- sClipRevealMethod = activityOptionsClass.getDeclaredMethod("makeClipRevealAnimation",
- View.class, int.class, int.class, int.class, int.class);
- } catch (Exception e) {
- // Earlier version
- }
- }
-
@Thunk Runnable mBuildLayersRunnable = new Runnable() {
public void run() {
if (mWorkspace != null) {
@@ -447,8 +423,7 @@
mIconCache = app.getIconCache();
mDragController = new DragController(this);
- mInflater = getLayoutInflater();
- mStateTransitionAnimation = new LauncherStateTransitionAnimation(this, this);
+ mStateTransitionAnimation = new LauncherStateTransitionAnimation(this);
mStats = new Stats(this);
@@ -652,10 +627,6 @@
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().
@@ -664,7 +635,7 @@
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public static int generateViewId() {
- if (Build.VERSION.SDK_INT >= 17) {
+ if (Utilities.ATLEAST_JB_MR1) {
return View.generateViewId();
} else {
// View.generateViewId() is not available. The following fallback logic is a copy
@@ -739,6 +710,7 @@
};
if (requestCode == REQUEST_BIND_APPWIDGET) {
+ // This is called only if the user did not previously have permissions to bind widgets
final int appWidgetId = data != null ?
data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1;
if (resultCode == RESULT_CANCELED) {
@@ -748,6 +720,10 @@
} else if (resultCode == RESULT_OK) {
addAppWidgetImpl(appWidgetId, mPendingAddInfo, null,
mPendingAddWidgetInfo, ON_ACTIVITY_RESULT_ANIMATION_DELAY);
+
+ // When the user has granted permission to bind widgets, we should check to see if
+ // we can inflate the default search bar widget.
+ getOrCreateQsbBar();
}
return;
} else if (requestCode == REQUEST_PICK_WALLPAPER) {
@@ -1314,16 +1290,9 @@
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 ?
@@ -1482,7 +1451,7 @@
* @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);
@@ -1542,23 +1511,6 @@
}
}
- private int[] getSpanForWidget(ComponentName component, int minWidth, int minHeight) {
- Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(this, component, null);
- // We want to account for the extra amount of padding that we are adding to the widget
- // to ensure that it gets the full amount of space that it has requested
- int requiredWidth = minWidth + padding.left + padding.right;
- int requiredHeight = minHeight + padding.top + padding.bottom;
- return CellLayout.rectToCell(this, requiredWidth, requiredHeight, null);
- }
-
- public int[] getSpanForWidget(AppWidgetProviderInfo info) {
- return getSpanForWidget(info.provider, info.minWidth, info.minHeight);
- }
-
- public int[] getMinSpanForWidget(AppWidgetProviderInfo info) {
- return getSpanForWidget(info.provider, info.minResizeWidth, info.minResizeHeight);
- }
-
/**
* Add a widget to the workspace.
*
@@ -1655,18 +1607,18 @@
}
registerReceiver(mReceiver, filter);
FirstFrameAnimatorHelper.initializeDrawListener(getWindow().getDecorView());
- setupTransparentSystemBarsForLmp();
+ setupTransparentSystemBarsForLollipop();
mAttached = true;
mVisible = true;
}
/**
- * Sets up transparent navigation and status bars in LMP.
+ * Sets up transparent navigation and status bars in Lollipop.
* This method is a no-op for other platform versions.
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
- private void setupTransparentSystemBarsForLmp() {
- if (Utilities.isLmpOrAbove()) {
+ private void setupTransparentSystemBarsForLollipop() {
+ if (Utilities.ATLEAST_LOLLIPOP) {
Window window = getWindow();
window.getAttributes().systemUiVisibility |=
(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
@@ -1745,11 +1697,11 @@
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);
@@ -1766,7 +1718,7 @@
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() {
@@ -1776,7 +1728,7 @@
}
i++;
}
- sendAdvanceMessage(mAdvanceInterval);
+ sendAdvanceMessage(ADVANCE_INTERVAL);
}
return true;
}
@@ -1833,7 +1785,7 @@
return mOverviewPanel;
}
- public SearchDropTargetBar getSearchBar() {
+ public SearchDropTargetBar getSearchDropTargetBar() {
return mSearchDropTargetBar;
}
@@ -1869,29 +1821,22 @@
super.onNewIntent(intent);
// Close the menu
- if (Intent.ACTION_MAIN.equals(intent.getAction())) {
+ Folder openFolder = mWorkspace.getOpenFolder();
+ boolean alreadyOnHome = mHasFocus && ((intent.getFlags() &
+ Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT)
+ != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
+ boolean isActionMain = Intent.ACTION_MAIN.equals(intent.getAction());
+ if (isActionMain) {
// also will cancel mWaitingForResult.
closeSystemDialogs();
- final boolean alreadyOnHome = mHasFocus && ((intent.getFlags() &
- Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT)
- != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
-
if (mWorkspace == null) {
// Can be cases where mWorkspace is null, this prevents a NPE
return;
}
- Folder openFolder = mWorkspace.getOpenFolder();
// In all these cases, only animate if we're already on home
mWorkspace.exitWidgetResizeMode();
- boolean moveToDefaultScreen = mLauncherCallbacks != null ?
- mLauncherCallbacks.shouldMoveToDefaultScreenOnHomeIntent() : true;
- if (alreadyOnHome && mState == State.WORKSPACE && !mWorkspace.isTouchActive() &&
- openFolder == null && moveToDefaultScreen) {
- mWorkspace.moveToDefaultScreen(true);
- }
-
closeFolder();
exitSpringLoadedDragMode();
@@ -1925,13 +1870,30 @@
}
}
- if (DEBUG_RESUME_TIME) {
- Log.d(TAG, "Time spent in onNewIntent: " + (System.currentTimeMillis() - startTime));
- }
-
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onNewIntent(intent);
}
+
+ // Defer moving to the default screen until after we callback to the LauncherCallbacks
+ // as slow logic in the callbacks eat into the time the scroller expects for the snapToPage
+ // animation.
+ if (isActionMain) {
+ boolean moveToDefaultScreen = mLauncherCallbacks != null ?
+ mLauncherCallbacks.shouldMoveToDefaultScreenOnHomeIntent() : true;
+ if (alreadyOnHome && mState == State.WORKSPACE && !mWorkspace.isTouchActive() &&
+ openFolder == null && moveToDefaultScreen) {
+ mWorkspace.post(new Runnable() {
+ @Override
+ public void run() {
+ mWorkspace.moveToDefaultScreen(true);
+ }
+ });
+ }
+ }
+
+ if (DEBUG_RESUME_TIME) {
+ Log.d(TAG, "Time spent in onNewIntent: " + (System.currentTimeMillis() - startTime));
+ }
}
@Override
@@ -1957,12 +1919,9 @@
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);
}
@@ -2139,6 +2098,15 @@
}
}
+ public void startSearchFromAllApps(View v, Intent searchIntent, String searchQuery) {
+ if (mLauncherCallbacks != null && mLauncherCallbacks.startSearchFromAllApps(searchQuery)) {
+ return;
+ }
+
+ // If not handled, then just start the provided search intent
+ startActivitySafely(v, searchIntent, null);
+ }
+
public boolean isOnCustomContent() {
return mWorkspace.isOnOrMovingToCustomContent();
}
@@ -2207,7 +2175,7 @@
mPendingAddInfo.screenId = -1;
mPendingAddInfo.cellX = mPendingAddInfo.cellY = -1;
mPendingAddInfo.spanX = mPendingAddInfo.spanY = -1;
- mPendingAddInfo.minSpanX = mPendingAddInfo.minSpanY = -1;
+ mPendingAddInfo.minSpanX = mPendingAddInfo.minSpanY = 1;
}
void addAppWidgetImpl(final int appWidgetId, final ItemInfo info, final
@@ -2539,6 +2507,10 @@
if (!isAppsViewVisible()) {
showAppsView(true /* animated */, false /* resetListToTop */,
true /* updatePredictedApps */, false /* focusSearchBar */);
+
+ if (mLauncherCallbacks != null) {
+ mLauncherCallbacks.onClickAllAppsButton(v);
+ }
}
}
@@ -2667,6 +2639,7 @@
throw new IllegalArgumentException("Input must be a FolderIcon");
}
+ // TODO(sunnygoyal): Re-evaluate this code.
FolderIcon folderIcon = (FolderIcon) v;
final FolderInfo info = folderIcon.getFolderInfo();
Folder openFolder = mWorkspace.getFolderForTag(info);
@@ -2870,8 +2843,7 @@
Bundle optsBundle = null;
if (useLaunchAnimation) {
ActivityOptions opts = null;
- if (sClipRevealMethod != null) {
- // TODO: call method directly when Launcher3 can depend on M APIs
+ if (Utilities.ATLEAST_MARSHMALLOW) {
int left = 0, top = 0;
int width = v.getMeasuredWidth(), height = v.getMeasuredHeight();
if (v instanceof TextView) {
@@ -2885,22 +2857,12 @@
height = bounds.height();
}
}
- try {
- opts = (ActivityOptions) sClipRevealMethod.invoke(null, v,
- left, top, width, height);
- } catch (IllegalAccessException e) {
- Log.d(TAG, "Could not call makeClipRevealAnimation: " + e);
- sClipRevealMethod = null;
- } catch (InvocationTargetException e) {
- Log.d(TAG, "Could not call makeClipRevealAnimation: " + e);
- sClipRevealMethod = null;
- }
- }
- if (opts == null && !Utilities.isLmpOrAbove()) {
+ opts = ActivityOptions.makeClipRevealAnimation(v, left, top, width, height);
+ } else if (!Utilities.ATLEAST_LOLLIPOP) {
// Below L, we use a scale up animation
opts = ActivityOptions.makeScaleUpAnimation(v, 0, 0,
v.getMeasuredWidth(), v.getMeasuredHeight());
- } else if (opts == null && Utilities.isLmpMR1()) {
+ } else if (Utilities.ATLEAST_LOLLIPOP_MR1) {
// On L devices, we use the device default slide-up transition.
// On L MR1 devices, we a custom version of the slide-up transition which
// doesn't have the delay present in the device default.
@@ -2929,7 +2891,7 @@
return false;
}
- @Thunk boolean startActivitySafely(View v, Intent intent, Object tag) {
+ public boolean startActivitySafely(View v, Intent intent, Object tag) {
boolean success = false;
if (mIsSafeModeEnabled && !Utilities.isSystemApp(this, intent)) {
Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();
@@ -2998,10 +2960,6 @@
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();
@@ -3013,9 +2971,9 @@
copyFolderIconToImage(fi);
fi.setVisibility(View.INVISIBLE);
- ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(mFolderIconImageView, alpha,
- scaleX, scaleY);
- if (Utilities.isLmpOrAbove()) {
+ ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(
+ mFolderIconImageView, 0, 1.5f, 1.5f);
+ if (Utilities.ATLEAST_LOLLIPOP) {
oa.setInterpolator(new LogDecelerateInterpolator(100, 0));
}
oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration));
@@ -3024,17 +2982,12 @@
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
@@ -3253,14 +3206,6 @@
}
}
- @Override
- public void onStateTransitionHideSearchBar() {
- // Hide the search bar
- if (mSearchDropTargetBar != null) {
- mSearchDropTargetBar.hideSearchBar(false /* animated */);
- }
- }
-
public void showWorkspace(boolean animated) {
showWorkspace(animated, null);
}
@@ -3269,16 +3214,9 @@
boolean changed = mState != State.WORKSPACE ||
mWorkspace.getState() != Workspace.State.NORMAL;
if (changed) {
- boolean wasInSpringLoadedMode = (mState != State.WORKSPACE);
mWorkspace.setVisibility(View.VISIBLE);
- mStateTransitionAnimation.startAnimationToWorkspace(mState, Workspace.State.NORMAL,
- animated, onCompleteRunnable);
-
- // Show the search bar (only animate if we were showing the drop target bar in spring
- // loaded mode)
- if (mSearchDropTargetBar != null) {
- mSearchDropTargetBar.showSearchBar(animated && wasInSpringLoadedMode);
- }
+ mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(),
+ Workspace.State.NORMAL, animated, onCompleteRunnable);
// Set focus to the AppsCustomize button
if (mAllAppsButton != null) {
@@ -3302,8 +3240,8 @@
void showOverviewMode(boolean animated) {
mWorkspace.setVisibility(View.VISIBLE);
- mStateTransitionAnimation.startAnimationToWorkspace(mState, Workspace.State.OVERVIEW,
- animated, null /* onCompleteRunnable */);
+ mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(),
+ Workspace.State.OVERVIEW, animated, null /* onCompleteRunnable */);
mState = State.WORKSPACE;
}
@@ -3356,9 +3294,10 @@
}
if (toState == State.APPS) {
- mStateTransitionAnimation.startAnimationToAllApps(animated, focusSearchBar);
+ mStateTransitionAnimation.startAnimationToAllApps(mWorkspace.getState(), animated,
+ focusSearchBar);
} else {
- mStateTransitionAnimation.startAnimationToWidgets(animated);
+ mStateTransitionAnimation.startAnimationToWidgets(mWorkspace.getState(), animated);
}
// Change the state *after* we've called all the transition code
@@ -3380,14 +3319,25 @@
* new state.
*/
public Animator startWorkspaceStateChangeAnimation(Workspace.State toState,
- boolean animated, boolean hasOverlaySearchBar, HashMap<View, Integer> layerViews) {
+ boolean animated, HashMap<View, Integer> layerViews) {
Workspace.State fromState = mWorkspace.getState();
- Animator anim = mWorkspace.setStateWithAnimation(
- toState, animated, hasOverlaySearchBar, 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 ||
@@ -3395,8 +3345,9 @@
return;
}
- mStateTransitionAnimation.startAnimationToWorkspace(mState, Workspace.State.SPRING_LOADED,
- true /* animated */, null /* onCompleteRunnable */);
+ mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(),
+ Workspace.State.SPRING_LOADED, true /* animated */,
+ null /* onCompleteRunnable */);
mState = isAppsViewVisible() ? State.APPS_SPRING_LOADED : State.WIDGETS_SPRING_LOADED;
}
@@ -3775,7 +3726,7 @@
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);
@@ -3878,7 +3829,8 @@
if (!mIsSafeModeEnabled
&& ((item.restoreStatus & LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) == 0)
- && ((item.restoreStatus & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID) != 0)) {
+ && (item.restoreStatus != LauncherAppWidgetInfo.RESTORE_COMPLETED)) {
+
if (appWidgetInfo == null) {
if (DEBUG_WIDGETS) {
Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
@@ -3888,42 +3840,51 @@
LauncherModel.deleteItemFromDatabase(this, item);
return;
}
- // Note: This assumes that the id remap broadcast is received before this step.
- // If that is not the case, the id remap will be ignored and user may see the
- // click to setup view.
- PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(this, appWidgetInfo, null);
- pendingInfo.spanX = item.spanX;
- pendingInfo.spanY = item.spanY;
- pendingInfo.minSpanX = item.minSpanX;
- pendingInfo.minSpanY = item.minSpanY;
- Bundle options = null;
- WidgetHostViewLoader.getDefaultOptionsForWidget(this, pendingInfo);
- int newWidgetId = mAppWidgetHost.allocateAppWidgetId();
- boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
- newWidgetId, appWidgetInfo, options);
+ // If we do not have a valid id, try to bind an id.
+ if ((item.restoreStatus & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID) != 0) {
+ // Note: This assumes that the id remap broadcast is received before this step.
+ // If that is not the case, the id remap will be ignored and user may see the
+ // click to setup view.
+ PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(this, appWidgetInfo, null);
+ pendingInfo.spanX = item.spanX;
+ pendingInfo.spanY = item.spanY;
+ pendingInfo.minSpanX = item.minSpanX;
+ pendingInfo.minSpanY = item.minSpanY;
+ Bundle options = null;
+ WidgetHostViewLoader.getDefaultOptionsForWidget(this, pendingInfo);
- // TODO consider showing a permission dialog when the widget is clicked.
- if (!success) {
- mAppWidgetHost.deleteAppWidgetId(newWidgetId);
- if (DEBUG_WIDGETS) {
- Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
- + " belongs to component " + item.providerName
- + ", as the launcher is unable to bing a new widget id");
+ int newWidgetId = mAppWidgetHost.allocateAppWidgetId();
+ boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
+ newWidgetId, appWidgetInfo, options);
+
+ // TODO consider showing a permission dialog when the widget is clicked.
+ if (!success) {
+ mAppWidgetHost.deleteAppWidgetId(newWidgetId);
+ if (DEBUG_WIDGETS) {
+ Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
+ + " belongs to component " + item.providerName
+ + ", as the launcher is unable to bing a new widget id");
+ }
+ LauncherModel.deleteItemFromDatabase(this, item);
+ return;
}
- LauncherModel.deleteItemFromDatabase(this, item);
- return;
+
+ item.appWidgetId = newWidgetId;
+
+ // If the widget has a configure activity, it is still needs to set it up, otherwise
+ // the widget is ready to go.
+ item.restoreStatus = (appWidgetInfo.configure == null)
+ ? LauncherAppWidgetInfo.RESTORE_COMPLETED
+ : LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
+
+ LauncherModel.updateItemInDatabase(this, item);
+ } else if (((item.restoreStatus & LauncherAppWidgetInfo.FLAG_UI_NOT_READY) != 0)
+ && (appWidgetInfo.configure == null)) {
+ // If the ID is already valid, verify if we need to configure or not.
+ item.restoreStatus = LauncherAppWidgetInfo.RESTORE_COMPLETED;
+ LauncherModel.updateItemInDatabase(this, item);
}
-
- item.appWidgetId = newWidgetId;
-
- // If the widget has a configure activity, it is still needs to set it up, otherwise
- // the widget is ready to go.
- item.restoreStatus = (appWidgetInfo.configure == null)
- ? LauncherAppWidgetInfo.RESTORE_COMPLETED
- : LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
-
- LauncherModel.updateItemInDatabase(this, item);
}
if (!mIsSafeModeEnabled && item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) {
@@ -3934,6 +3895,8 @@
}
item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
+ item.minSpanX = appWidgetInfo.minSpanX;
+ item.minSpanY = appWidgetInfo.minSpanY;
} else {
appWidgetInfo = null;
PendingAppWidgetHostView view = new PendingAppWidgetHostView(this, item,
@@ -4060,10 +4023,7 @@
}
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));
@@ -4078,7 +4038,7 @@
return mDeviceProfile.getSearchBarBounds(Utilities.isRtl(getResources()));
}
- public void bindSearchablesChanged() {
+ public void bindSearchProviderChanged() {
if (mSearchDropTargetBar == null) {
return;
}
@@ -4301,13 +4261,14 @@
return oriMap[(d.getRotation() + indexOffset) % 4];
}
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public void lockScreenOrientation() {
if (mRotationEnabled) {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
+ if (Utilities.ATLEAST_JB_MR2) {
+ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
+ } else {
setRequestedOrientation(mapConfigurationOriActivityInfoOri(getResources()
.getConfiguration().orientation));
- } else {
- setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
}
}
}
@@ -4321,7 +4282,7 @@
public void run() {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
}
- }, mRestoreScreenOrientationDelay);
+ }, RESTORE_SCREEN_ORIENTATION_DELAY);
}
}
}
@@ -4494,14 +4455,16 @@
if (mWorkspace != null) mWorkspace.setAlpha(1f);
if (mHotseat != null) mHotseat.setAlpha(1f);
if (mPageIndicators != null) mPageIndicators.setAlpha(1f);
- if (mSearchDropTargetBar != null) mSearchDropTargetBar.showSearchBar(false);
+ if (mSearchDropTargetBar != null) mSearchDropTargetBar.animateToState(
+ SearchDropTargetBar.State.SEARCH_BAR, 0);
}
void hideWorkspaceSearchAndHotseat() {
if (mWorkspace != null) mWorkspace.setAlpha(0f);
if (mHotseat != null) mHotseat.setAlpha(0f);
if (mPageIndicators != null) mPageIndicators.setAlpha(0f);
- if (mSearchDropTargetBar != null) mSearchDropTargetBar.hideSearchBar(false);
+ if (mSearchDropTargetBar != null) mSearchDropTargetBar.animateToState(
+ SearchDropTargetBar.State.INVISIBLE, 0);
}
// TODO: These method should be a part of LauncherSearchCallback
@@ -4509,7 +4472,7 @@
public ItemInfo createAppDragInfo(Intent appLaunchIntent) {
// Called from search suggestion
UserHandleCompat user = null;
- if (Utilities.isLmpOrAbove()) {
+ if (Utilities.ATLEAST_LOLLIPOP) {
UserHandle userHandle = appLaunchIntent.getParcelableExtra(Intent.EXTRA_USER);
if (userHandle != null) {
user = UserHandleCompat.fromUser(userHandle);
diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java
index 6a248a3..853c2ec 100644
--- a/src/com/android/launcher3/LauncherAnimUtils.java
+++ b/src/com/android/launcher3/LauncherAnimUtils.java
@@ -21,14 +21,10 @@
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 @@
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 0b7b1fd..5a7fadb 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -17,7 +17,7 @@
package com.android.launcher3;
import android.app.SearchManager;
-import android.content.ComponentName;
+import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -26,6 +26,7 @@
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.Thunk;
import java.lang.ref.WeakReference;
@@ -33,12 +34,11 @@
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;
@@ -87,7 +87,6 @@
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);
@@ -96,12 +95,22 @@
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_LOCALE_CHANGED);
filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
- filter.addAction(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED);
// For handling managed profiles
filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED);
filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED);
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));
+ }
}
/**
@@ -126,7 +135,7 @@
LauncherModel setLauncher(Launcher launcher) {
getLauncherProvider().setLauncherProviderChangeListener(launcher);
mModel.initialize(launcher);
- mAccessibilityDelegate = ((launcher != null) && Utilities.isLmpOrAbove()) ?
+ mAccessibilityDelegate = ((launcher != null) && Utilities.ATLEAST_LOLLIPOP) ?
new LauncherAccessibilityDelegate(launcher) : null;
return mModel;
}
@@ -147,7 +156,7 @@
sLauncherProvider = new WeakReference<LauncherProvider>(provider);
}
- static LauncherProvider getLauncherProvider() {
+ public static LauncherProvider getLauncherProvider() {
return sLauncherProvider.get();
}
@@ -158,10 +167,6 @@
public WidgetPreviewLoader getWidgetCache() {
return mWidgetCache;
}
-
- public void onWallpaperChanged() {
- mWallpaperChangedSinceLastCheck = true;
- }
public boolean hasWallpaperChangedSinceLastCheck() {
boolean result = mWallpaperChangedSinceLastCheck;
@@ -172,8 +177,4 @@
public InvariantDeviceProfile getInvariantDeviceProfile() {
return mInvariantDeviceProfile;
}
-
- public static boolean isDogfoodBuild() {
- return getInstance().mBuildInfo.isDogfoodBuild();
- }
}
diff --git a/src/com/android/launcher3/LauncherAppWidgetHost.java b/src/com/android/launcher3/LauncherAppWidgetHost.java
index de7c610..6c3a1e8 100644
--- a/src/com/android/launcher3/LauncherAppWidgetHost.java
+++ b/src/com/android/launcher3/LauncherAppWidgetHost.java
@@ -95,8 +95,6 @@
}
protected void onProvidersChanged() {
- mLauncher.getModel().loadAndBindWidgetsAndShortcuts(mLauncher, mLauncher,
- true /* refresh */);
if (!mProviderChangeListeners.isEmpty()) {
for (Runnable callback : new ArrayList<>(mProviderChangeListeners)) {
callback.run();
diff --git a/src/com/android/launcher3/LauncherAppWidgetHostView.java b/src/com/android/launcher3/LauncherAppWidgetHostView.java
index cf461a5..d8cb682 100644
--- a/src/com/android/launcher3/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/LauncherAppWidgetHostView.java
@@ -26,7 +26,8 @@
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}
diff --git a/src/com/android/launcher3/LauncherAppWidgetInfo.java b/src/com/android/launcher3/LauncherAppWidgetInfo.java
index aad18b5..882f7e2 100644
--- a/src/com/android/launcher3/LauncherAppWidgetInfo.java
+++ b/src/com/android/launcher3/LauncherAppWidgetInfo.java
@@ -68,10 +68,6 @@
ComponentName providerName;
- // TODO: Are these necessary here?
- int minWidth = -1;
- int minHeight = -1;
-
/**
* Indicates the restore status of the widget.
*/
diff --git a/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java b/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
index 85af92f..71a08a8 100644
--- a/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
+++ b/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
@@ -1,10 +1,13 @@
package com.android.launcher3;
import android.annotation.TargetApi;
+import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.graphics.Point;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Parcel;
@@ -19,10 +22,10 @@
public boolean isCustomWidget = false;
- private int mSpanX = -1;
- private int mSpanY = -1;
- private int mMinSpanX = -1;
- private int mMinSpanY = -1;
+ public int spanX;
+ public int spanY;
+ public int minSpanX;
+ public int minSpanY;
public static LauncherAppWidgetProviderInfo fromProviderInfo(Context context,
AppWidgetProviderInfo info) {
@@ -41,6 +44,7 @@
public LauncherAppWidgetProviderInfo(Parcel in) {
super(in);
+ initSpans();
}
public LauncherAppWidgetProviderInfo(Context context, CustomAppWidget widget) {
@@ -52,6 +56,41 @@
previewImage = widget.getPreviewImage();
initialLayout = widget.getWidgetLayout();
resizeMode = widget.getResizeMode();
+ initSpans();
+ }
+
+ private void initSpans() {
+ LauncherAppState app = LauncherAppState.getInstance();
+ InvariantDeviceProfile idp = app.getInvariantDeviceProfile();
+
+ // We only care out the cell size, which is independent of the the layout direction.
+ Rect paddingLand = idp.landscapeProfile.getWorkspacePadding(false /* isLayoutRtl */);
+ Rect paddingPort = idp.portraitProfile.getWorkspacePadding(false /* isLayoutRtl */);
+
+ // Always assume we're working with the smallest span to make sure we
+ // reserve enough space in both orientations.
+ float smallestCellWidth = DeviceProfile.calculateCellWidth(Math.min(
+ idp.landscapeProfile.widthPx - paddingLand.left - paddingLand.right,
+ idp.portraitProfile.widthPx - paddingPort.left - paddingPort.right),
+ idp.numColumns);
+ float smallestCellHeight = DeviceProfile.calculateCellWidth(Math.min(
+ idp.landscapeProfile.heightPx - paddingLand.top - paddingLand.bottom,
+ idp.portraitProfile.heightPx - paddingPort.top - paddingPort.bottom),
+ idp.numRows);
+
+ // We want to account for the extra amount of padding that we are adding to the widget
+ // to ensure that it gets the full amount of space that it has requested.
+ Rect widgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(
+ app.getContext(), provider, null);
+ spanX = Math.max(1, (int) Math.ceil(
+ (minWidth + widgetPadding.left + widgetPadding.right) / smallestCellWidth));
+ spanY = Math.max(1, (int) Math.ceil(
+ (minHeight + widgetPadding.top + widgetPadding.bottom) / smallestCellHeight));
+
+ minSpanX = Math.max(1, (int) Math.ceil(
+ (minResizeWidth + widgetPadding.left + widgetPadding.right) / smallestCellWidth));
+ minSpanY = Math.max(1, (int) Math.ceil(
+ (minResizeHeight + widgetPadding.top + widgetPadding.bottom) / smallestCellHeight));
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@@ -79,35 +118,9 @@
provider.toString(), provider.getPackageName(), provider.getShortClassName(), getLabel(pm));
}
- public int getSpanX(Launcher launcher) {
- lazyLoadSpans(launcher);
- return mSpanX;
- }
-
- public int getSpanY(Launcher launcher) {
- lazyLoadSpans(launcher);
- return mSpanY;
- }
-
- public int getMinSpanX(Launcher launcher) {
- lazyLoadSpans(launcher);
- return mMinSpanX;
- }
-
- public int getMinSpanY(Launcher launcher) {
- lazyLoadSpans(launcher);
- return mMinSpanY;
- }
-
- private void lazyLoadSpans(Launcher launcher) {
- if (mSpanX < 0 || mSpanY < 0 || mMinSpanX < 0 || mMinSpanY < 0) {
- int[] minResizeSpan = launcher.getMinSpanForWidget(this);
- int[] span = launcher.getSpanForWidget(this);
-
- mSpanX = span[0];
- mSpanY = span[1];
- mMinSpanX = minResizeSpan[0];
- mMinSpanY = minResizeSpan[1];
- }
+ public Point getMinSpans(InvariantDeviceProfile idp, Context context) {
+ return new Point(
+ (resizeMode & RESIZE_HORIZONTAL) != 0 ? minSpanX : -1,
+ (resizeMode & RESIZE_VERTICAL) != 0 ? minSpanY : -1);
}
}
diff --git a/src/com/android/launcher3/LauncherBackupAgentHelper.java b/src/com/android/launcher3/LauncherBackupAgentHelper.java
index a92a889..8eb4e63 100644
--- a/src/com/android/launcher3/LauncherBackupAgentHelper.java
+++ b/src/com/android/launcher3/LauncherBackupAgentHelper.java
@@ -24,6 +24,8 @@
import android.os.ParcelFileDescriptor;
import android.util.Log;
+import com.android.launcher3.model.MigrateFromRestoreTask;
+
import java.io.IOException;
public class LauncherBackupAgentHelper extends BackupAgentHelper {
@@ -63,7 +65,7 @@
@Override
public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
throws IOException {
- if (!Utilities.isLmpOrAbove()) {
+ if (!Utilities.ATLEAST_LOLLIPOP) {
// No restore for old devices.
Log.i(TAG, "You shall not pass!!!");
Log.d(TAG, "Restore is only supported on devices running Lollipop and above.");
@@ -91,11 +93,19 @@
LauncherAppState.getLauncherProvider().clearFlagEmptyDbCreated();
LauncherClings.synchonouslyMarkFirstRunClingDismissed(this);
- // TODO: Update the backup set to include rank.
+ // Rank was added in v4.
if (mHelper.restoredBackupVersion <= 3) {
LauncherAppState.getLauncherProvider().updateFolderItemsRank();
- LauncherAppState.getLauncherProvider().convertShortcutsToLauncherActivities();
}
+
+ if (MigrateFromRestoreTask.ENABLED && mHelper.shouldAttemptWorkspaceMigration()) {
+ MigrateFromRestoreTask.markForMigration(getApplicationContext(),
+ (int) mHelper.migrationCompatibleProfileData.desktopCols,
+ (int) mHelper.migrationCompatibleProfileData.desktopRows,
+ mHelper.widgetSizes);
+ }
+
+ LauncherAppState.getLauncherProvider().convertShortcutsToLauncherActivities();
} else {
if (VERBOSE) Log.v(TAG, "Nothing was restored, clearing DB");
LauncherAppState.getLauncherProvider().createEmptyDB();
diff --git a/src/com/android/launcher3/LauncherBackupHelper.java b/src/com/android/launcher3/LauncherBackupHelper.java
index 38a4bde..2d11d3a 100644
--- a/src/com/android/launcher3/LauncherBackupHelper.java
+++ b/src/com/android/launcher3/LauncherBackupHelper.java
@@ -32,6 +32,7 @@
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
+import android.graphics.Point;
import android.graphics.drawable.Drawable;
import android.os.ParcelFileDescriptor;
import android.text.TextUtils;
@@ -51,6 +52,7 @@
import com.android.launcher3.backup.BackupProtos.Widget;
import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.model.MigrateFromRestoreTask;
import com.android.launcher3.util.Thunk;
import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
import com.google.protobuf.nano.MessageNano;
@@ -75,7 +77,7 @@
private static final boolean VERBOSE = LauncherBackupAgentHelper.VERBOSE;
private static final boolean DEBUG = LauncherBackupAgentHelper.DEBUG;
- private static final int BACKUP_VERSION = 3;
+ private static final int BACKUP_VERSION = 4;
private static final int MAX_JOURNAL_SIZE = 1000000;
// Journal key is such that it is always smaller than any dynamically generated
@@ -107,6 +109,7 @@
Favorites.SPANY, // 15
Favorites.TITLE, // 16
Favorites.PROFILE_ID, // 17
+ Favorites.RANK, // 18
};
private static final int ID_INDEX = 0;
@@ -126,6 +129,7 @@
private static final int SPANX_INDEX = 14;
private static final int SPANY_INDEX = 15;
private static final int TITLE_INDEX = 16;
+ private static final int RANK_INDEX = 18;
private static final String[] SCREEN_PROJECTION = {
WorkspaceScreens._ID, // 0
@@ -150,6 +154,9 @@
private DeviceProfieData mDeviceProfileData;
private InvariantDeviceProfile mIdp;
+ DeviceProfieData migrationCompatibleProfileData;
+ HashSet<String> widgetSizes = new HashSet<>();
+
boolean restoreSuccessful;
int restoredBackupVersion = 1;
@@ -286,9 +293,9 @@
return true;
}
- boolean isHotsetCompatible = false;
+ boolean isHotseatCompatible = false;
if (currentProfile.allappsRank >= oldProfile.hotseatCount) {
- isHotsetCompatible = true;
+ isHotseatCompatible = true;
mHotseatShift = 0;
}
@@ -296,12 +303,29 @@
&& ((currentProfile.hotseatCount - currentProfile.allappsRank) >=
(oldProfile.hotseatCount - oldProfile.allappsRank))) {
// There is enough space on both sides of the hotseat.
- isHotsetCompatible = true;
+ isHotseatCompatible = true;
mHotseatShift = currentProfile.allappsRank - oldProfile.allappsRank;
}
- return isHotsetCompatible && (currentProfile.desktopCols >= oldProfile.desktopCols)
- && (currentProfile.desktopRows >= oldProfile.desktopRows);
+ if (!isHotseatCompatible) {
+ return false;
+ }
+ if ((currentProfile.desktopCols >= oldProfile.desktopCols)
+ && (currentProfile.desktopRows >= oldProfile.desktopRows)) {
+ return true;
+ }
+
+ if (MigrateFromRestoreTask.ENABLED &&
+ (oldProfile.desktopCols - currentProfile.desktopCols <= 1) &&
+ (oldProfile.desktopRows - currentProfile.desktopRows <= 1)) {
+ // Allow desktop migration when row and/or column count contracts by 1.
+
+ migrationCompatibleProfileData = initDeviceProfileData(mIdp);
+ migrationCompatibleProfileData.desktopCols = oldProfile.desktopCols;
+ migrationCompatibleProfileData.desktopRows = oldProfile.desktopRows;
+ return true;
+ }
+ return false;
}
/**
@@ -441,7 +465,10 @@
Key key = getKey(Key.FAVORITE, id);
mKeys.add(key);
final String backupKey = keyToBackupKey(key);
- if (!mExistingKeys.contains(backupKey) || updateTime >= mLastBackupRestoreTime) {
+
+ // Favorite proto changed in v4. Backup again if the version is old.
+ if (!mExistingKeys.contains(backupKey) || updateTime >= mLastBackupRestoreTime
+ || restoredBackupVersion < 4) {
writeRowToBackup(key, packFavorite(cursor), data);
} else {
if (DEBUG) Log.d(TAG, "favorite already backup up: " + id);
@@ -648,7 +675,9 @@
} else {
Log.w(TAG, "empty intent on appwidget: " + id);
}
- if (mExistingKeys.contains(backupKey) && restoredBackupVersion >= BACKUP_VERSION) {
+
+ // Widget backup proto changed in v3. So add it again if the original backup is old.
+ if (mExistingKeys.contains(backupKey) && restoredBackupVersion >= 3) {
if (DEBUG) Log.d(TAG, "already saved widget " + backupKey);
// remember that we already backed this up previously
@@ -699,7 +728,8 @@
}
}
- // future site of widget table mutation
+ // Cache widget min sizes incase migration is required.
+ widgetSizes.add(widget.provider + "#" + widget.minSpanX + "," + widget.minSpanY);
}
/** create a new key, with an integer ID.
@@ -783,6 +813,7 @@
favorite.spanX = c.getInt(SPANX_INDEX);
favorite.spanY = c.getInt(SPANY_INDEX);
favorite.iconType = c.getInt(ICON_TYPE_INDEX);
+ favorite.rank = c.getInt(RANK_INDEX);
String title = c.getString(TITLE_INDEX);
if (!TextUtils.isEmpty(title)) {
@@ -870,6 +901,7 @@
values.put(Favorites.CELLY, favorite.cellY);
values.put(Favorites.SPANX, favorite.spanX);
values.put(Favorites.SPANY, favorite.spanY);
+ values.put(Favorites.RANK, favorite.rank);
if (favorite.itemType == Favorites.ITEM_TYPE_SHORTCUT) {
values.put(Favorites.ICON_TYPE, favorite.iconType);
@@ -895,7 +927,11 @@
UserManagerCompat.getInstance(mContext).getSerialNumberForUser(myUserHandle);
values.put(LauncherSettings.Favorites.PROFILE_ID, userSerialNumber);
- DeviceProfieData currentProfile = mDeviceProfileData;
+ // If we will attempt grid resize, use the original profile to validate grid size, as
+ // anything which fits in the original grid should fit in the current grid after
+ // grid migration.
+ DeviceProfieData currentProfile = migrationCompatibleProfileData == null
+ ? mDeviceProfileData : migrationCompatibleProfileData;
if (favorite.itemType == Favorites.ITEM_TYPE_APPWIDGET) {
if (!TextUtils.isEmpty(favorite.appWidgetProvider)) {
@@ -987,14 +1023,9 @@
widget.icon.dpi = dpi;
}
- // Calculate the spans corresponding to any one of the orientations as it should not change
- // based on orientation.
- int[] minSpans = CellLayout.rectToCell(
- mIdp.portraitProfile, mContext, info.minResizeWidth, info.minResizeHeight, null);
- widget.minSpanX = (info.resizeMode & LauncherAppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0
- ? minSpans[0] : -1;
- widget.minSpanY = (info.resizeMode & LauncherAppWidgetProviderInfo.RESIZE_VERTICAL) != 0
- ? minSpans[1] : -1;
+ Point spans = info.getMinSpans(mIdp, mContext);
+ widget.minSpanX = spans.x;
+ widget.minSpanY = spans.y;
return widget;
}
@@ -1179,6 +1210,10 @@
}
}
+ public boolean shouldAttemptWorkspaceMigration() {
+ return migrationCompatibleProfileData != null;
+ }
+
/**
* A class to check if an activity can handle one of the intents from a list of
* predefined intents.
diff --git a/src/com/android/launcher3/LauncherCallbacks.java b/src/com/android/launcher3/LauncherCallbacks.java
index 6618cca..e34bd57 100644
--- a/src/com/android/launcher3/LauncherCallbacks.java
+++ b/src/com/android/launcher3/LauncherCallbacks.java
@@ -77,6 +77,7 @@
public boolean providesSearch();
public boolean startSearch(String initialQuery, boolean selectInitialQuery,
Bundle appSearchData, Rect sourceBounds);
+ public boolean startSearchFromAllApps(String query);
@Deprecated
public void startVoice();
public boolean hasCustomContentToLeft();
diff --git a/src/com/android/launcher3/LauncherClings.java b/src/com/android/launcher3/LauncherClings.java
index c13752c..458844c 100644
--- a/src/com/android/launcher3/LauncherClings.java
+++ b/src/com/android/launcher3/LauncherClings.java
@@ -16,8 +16,6 @@
package com.android.launcher3;
-import android.accounts.Account;
-import android.accounts.AccountManager;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.annotation.TargetApi;
@@ -29,23 +27,24 @@
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;
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";
private static final String TAG_CROP_TOP_AND_SIDES = "crop_bg_top_and_sides";
- private static final boolean DISABLE_CLINGS = false;
-
private static final int SHOW_CLING_DURATION = 250;
private static final int DISMISS_CLING_DURATION = 200;
@@ -87,6 +86,20 @@
}
}
+ @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.
*
@@ -94,6 +107,7 @@
* package was not preinstalled and there exists a db to migrate from.
*/
public void showMigrationCling() {
+ mLauncher.onLauncherClingShown();
mLauncher.hideWorkspaceSearchAndHotseat();
ViewGroup root = (ViewGroup) mLauncher.findViewById(R.id.launcher);
@@ -136,7 +150,9 @@
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 +160,7 @@
content.setBackground(bg);
}
+ mLauncher.onLauncherClingShown();
root.addView(cling);
if (showWelcome) {
@@ -161,12 +178,12 @@
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 +197,12 @@
@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);
}
};
@@ -215,10 +237,6 @@
/** Returns whether the clings are enabled or should be shown */
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
private boolean areClingsEnabled() {
- if (DISABLE_CLINGS) {
- return false;
- }
-
// disable clings when running in a test harness
if(ActivityManager.isRunningInTestHarness()) return false;
@@ -231,10 +249,7 @@
// Restricted secondary users (child mode) will potentially have very few apps
// seeded when they start up for the first time. Clings won't work well with that
- boolean supportsLimitedUsers =
- android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
- Account[] accounts = AccountManager.get(mLauncher).getAccounts();
- if (supportsLimitedUsers && accounts.length == 0) {
+ if (Utilities.ATLEAST_JB_MR2) {
UserManager um = (UserManager) mLauncher.getSystemService(Context.USER_SERVICE);
Bundle restrictions = um.getUserRestrictions();
if (restrictions.getBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS, false)) {
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 1fbb06e..b390f13 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -17,6 +17,7 @@
package com.android.launcher3;
import android.app.SearchManager;
+import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -33,7 +34,6 @@
import android.database.Cursor;
import android.graphics.Bitmap;
import android.net.Uri;
-import android.os.Build;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
@@ -55,6 +55,8 @@
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;
import com.android.launcher3.util.CursorIconInfo;
@@ -203,7 +205,7 @@
public void bindComponentsRemoved(ArrayList<String> packageNames,
ArrayList<AppInfo> appInfos, UserHandleCompat user, int reason);
public void bindAllPackages(WidgetsModel model);
- public void bindSearchablesChanged();
+ public void bindSearchProviderChanged();
public boolean isAllAppsButtonRank(int rank);
public void onPageBoundSynchronously(int page);
public void dumpLogsToLocalData();
@@ -887,7 +889,7 @@
}
private void assertWorkspaceLoaded() {
- if (LauncherAppState.isDogfoodBuild() && (isLoadingWorkspace() || !mHasLoaderCompletedOnce)) {
+ if (ProviderConfig.IS_DOGFOOD_BUILD && (isLoadingWorkspace() || !mHasLoaderCompletedOnce)) {
throw new RuntimeException("Trying to add shortcut while loader is running");
}
}
@@ -1128,7 +1130,7 @@
* Update the order of the workspace screens in the database. The array list contains
* a list of screen ids in the order that they should appear.
*/
- void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) {
+ public void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) {
final ArrayList<Long> screensCopy = new ArrayList<Long>(screens);
final ContentResolver cr = context.getContentResolver();
final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
@@ -1275,14 +1277,14 @@
if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
// If we have changed locale we need to clear out the labels in all apps/workspace.
forceReload();
- } else if (SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED.equals(action) ||
- SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED.equals(action)) {
+ } else if (SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED.equals(action)) {
Callbacks callbacks = getCallback();
if (callbacks != null) {
- callbacks.bindSearchablesChanged();
+ callbacks.bindSearchProviderChanged();
}
} else if (LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED.equals(action)
|| LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
+ UserManagerCompat.getInstance(context).enableAndResetCache();
forceReload();
}
}
@@ -1406,7 +1408,7 @@
/**
* Loads the workspace screen ids in an ordered list.
*/
- @Thunk static ArrayList<Long> loadWorkspaceScreensDb(Context context) {
+ public static ArrayList<Long> loadWorkspaceScreensDb(Context context) {
final ContentResolver contentResolver = context.getContentResolver();
final Uri screensUri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
@@ -1744,14 +1746,32 @@
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();
int countX = (int) profile.numColumns;
int countY = (int) profile.numRows;
+ if (MigrateFromRestoreTask.ENABLED && MigrateFromRestoreTask.shouldRunTask(mContext)) {
+ long migrationStartTime = System.currentTimeMillis();
+ Log.v(TAG, "Starting workspace migration after restore");
+ try {
+ MigrateFromRestoreTask task = new MigrateFromRestoreTask(mContext);
+ // Clear the flags before starting the task, so that we do not run the task
+ // again, in case there was an uncaught error.
+ MigrateFromRestoreTask.clearFlags(mContext);
+ task.execute();
+ } catch (Exception e) {
+ Log.e(TAG, "Error during grid migration", e);
+
+ // Clear workspace.
+ mFlags = mFlags | LOADER_FLAG_CLEAR_WORKSPACE;
+ }
+ Log.v(TAG, "Workspace migration completed in "
+ + (System.currentTimeMillis() - migrationStartTime));
+ }
+
if ((mFlags & LOADER_FLAG_CLEAR_WORKSPACE) != 0) {
Launcher.addDumpLog(TAG, "loadWorkspace: resetting launcher database", true);
LauncherAppState.getLauncherProvider().deleteDatabase();
@@ -2169,7 +2189,7 @@
// Id would be valid only if the widget restore broadcast was received.
if (isIdValid) {
- status = LauncherAppWidgetInfo.RESTORE_COMPLETED;
+ status = LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
} else {
status &= ~LauncherAppWidgetInfo
.FLAG_PROVIDER_NOT_READY;
@@ -2278,6 +2298,21 @@
}
}
+ // Sort all the folder items and make sure the first 3 items are high resolution.
+ for (FolderInfo folder : sBgFolders) {
+ Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR);
+ int pos = 0;
+ for (ShortcutInfo info : folder.contents) {
+ if (info.usingLowResIcon) {
+ info.updateIcon(mIconCache, false);
+ }
+ pos ++;
+ if (pos >= FolderIcon.NUM_ITEMS_IN_PREVIEW) {
+ break;
+ }
+ }
+ }
+
if (restoredRows.size() > 0) {
// Update restored items that no longer require special handling
ContentValues values = new ContentValues();
@@ -2289,7 +2324,7 @@
if (!isSdCardReady && !sPendingPackages.isEmpty()) {
context.registerReceiver(new AppsAvailabilityCheck(),
- new IntentFilter(StartupReceiver.SYSTEM_READY),
+ new IntentFilter(Intent.ACTION_BOOT_COMPLETED),
null, sWorker);
}
@@ -2461,7 +2496,7 @@
return (int) (lhs.screenId - rhs.screenId);
}
default:
- if (LauncherAppState.isDogfoodBuild()) {
+ if (ProviderConfig.IS_DOGFOOD_BUILD) {
throw new RuntimeException("Unexpected container type when " +
"sorting workspace items.");
}
@@ -2841,8 +2876,7 @@
// Cleanup any data stored for a deleted user.
ManagedProfileHeuristic.processAllUsers(profiles, mContext);
- loadAndBindWidgetsAndShortcuts(mApp.getContext(), tryGetCallbacks(oldCallbacks),
- true /* refresh */);
+ loadAndBindWidgetsAndShortcuts(tryGetCallbacks(oldCallbacks), true /* refresh */);
if (DEBUG_LOADERS) {
Log.d(TAG, "Icons processed in "
+ (SystemClock.uptimeMillis() - loadTime) + "ms");
@@ -2911,7 +2945,7 @@
}
// Reload widget list. No need to refresh, as we only want to update the icons and labels.
- loadAndBindWidgetsAndShortcuts(mApp.getContext(), callbacks, false);
+ loadAndBindWidgetsAndShortcuts(callbacks, false);
}
void enqueuePackageUpdated(PackageUpdatedTask task) {
@@ -3174,6 +3208,12 @@
widgetInfo.restoreStatus &=
~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY &
~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
+
+ // adding this flag ensures that launcher shows 'click to setup'
+ // if the widget has a config activity. In case there is no config
+ // activity, it will be marked as 'restored' during bind.
+ widgetInfo.restoreStatus |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
+
widgets.add(widgetInfo);
updateItemInDatabase(context, widgetInfo);
}
@@ -3253,8 +3293,36 @@
});
}
- // onProvidersChanged method (API >= 17) already refreshed the widget list
- loadAndBindWidgetsAndShortcuts(context, callbacks, Build.VERSION.SDK_INT < 17);
+ // Update widgets
+ if (mOp == OP_ADD || mOp == OP_REMOVE || mOp == OP_UPDATE) {
+ // Always refresh for a package event on secondary user
+ boolean needToRefresh = !mUser.equals(UserHandleCompat.myUserHandle());
+
+ // Refresh widget list, if the package already had a widget.
+ synchronized (sBgLock) {
+ if (sBgWidgetProviders != null) {
+ HashSet<String> pkgSet = new HashSet<>();
+ Collections.addAll(pkgSet, mPackages);
+
+ for (ComponentKey key : sBgWidgetProviders.keySet()) {
+ needToRefresh |= key.user.equals(mUser) &&
+ pkgSet.contains(key.componentName.getPackageName());
+ }
+ }
+ }
+
+ if (!needToRefresh && mOp != OP_REMOVE) {
+ // Refresh widget list, if there is any newly added widget
+ PackageManager pm = context.getPackageManager();
+ for (String pkg : mPackages) {
+ needToRefresh |= !pm.queryBroadcastReceivers(
+ new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE)
+ .setPackage(pkg), 0).isEmpty();
+ }
+ }
+
+ loadAndBindWidgetsAndShortcuts(callbacks, needToRefresh);
+ }
// Write all the logs to disk
mHandler.post(new Runnable() {
@@ -3328,13 +3396,12 @@
}
}
- public void loadAndBindWidgetsAndShortcuts(final Context context, final Callbacks callbacks,
- final boolean refresh) {
+ public void loadAndBindWidgetsAndShortcuts(final Callbacks callbacks, final boolean refresh) {
runOnWorkerThread(new Runnable() {
@Override
public void run() {
- updateWidgetsModel(context, refresh);
+ updateWidgetsModel(refresh);
final WidgetsModel model = mBgWidgetsModel.clone();
mHandler.post(new Runnable() {
@@ -3358,10 +3425,10 @@
*
* @see #loadAndBindWidgetsAndShortcuts
*/
- @Thunk void updateWidgetsModel(Context context, boolean refresh) {
- PackageManager packageManager = context.getPackageManager();
+ @Thunk void updateWidgetsModel(boolean refresh) {
+ PackageManager packageManager = mApp.getContext().getPackageManager();
final ArrayList<Object> widgetsAndShortcuts = new ArrayList<Object>();
- widgetsAndShortcuts.addAll(getWidgetProviders(context, refresh));
+ widgetsAndShortcuts.addAll(getWidgetProviders(mApp.getContext(), refresh));
Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
widgetsAndShortcuts.addAll(packageManager.queryIntentActivities(shortcutsIntent, 0));
mBgWidgetsModel.setWidgetsAndShortcuts(widgetsAndShortcuts);
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index cc5e18b..44a43e1 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -71,14 +71,12 @@
private static final int DATABASE_VERSION = 26;
static final String OLD_AUTHORITY = "com.android.launcher2.settings";
- static final String AUTHORITY = ProviderConfig.AUTHORITY;
+ public static final String AUTHORITY = ProviderConfig.AUTHORITY;
static final String TABLE_FAVORITES = LauncherSettings.Favorites.TABLE_NAME;
static final String TABLE_WORKSPACE_SCREENS = LauncherSettings.WorkspaceScreens.TABLE_NAME;
static final String EMPTY_DATABASE_CREATED = "EMPTY_DATABASE_CREATED";
- private static final String URI_PARAM_IS_EXTERNAL_ADD = "isExternalAdd";
-
private static final String RESTRICTION_PACKAGE_NAME = "workspace.configuration.package.name";
@Thunk LauncherProviderChangeListener mListener;
@@ -140,14 +138,21 @@
return db.insert(table, nullColumnHack, values);
}
+ private void reloadLauncherIfExternal() {
+ if (Binder.getCallingPid() != Process.myPid()) {
+ LauncherAppState app = LauncherAppState.getInstanceNoCreate();
+ if (app != null) {
+ app.reloadWorkspace();
+ }
+ }
+ }
+
@Override
public Uri insert(Uri uri, ContentValues initialValues) {
SqlArguments args = new SqlArguments(uri);
- // In very limited cases, we support system|signature permission apps to add to the db
- String externalAdd = uri.getQueryParameter(URI_PARAM_IS_EXTERNAL_ADD);
- final boolean isExternalAll = externalAdd != null && "true".equals(externalAdd);
- if (isExternalAll) {
+ // In very limited cases, we support system|signature permission apps to modify the db.
+ if (Binder.getCallingPid() != Process.myPid()) {
if (!mOpenHelper.initializeExternalAdd(initialValues)) {
return null;
}
@@ -161,13 +166,7 @@
uri = ContentUris.withAppendedId(uri, rowId);
notifyListeners();
- if (isExternalAll) {
- LauncherAppState app = LauncherAppState.getInstanceNoCreate();
- if (app != null) {
- app.reloadWorkspace();
- }
- }
-
+ reloadLauncherIfExternal();
return uri;
}
@@ -192,6 +191,7 @@
}
notifyListeners();
+ reloadLauncherIfExternal();
return values.length;
}
@@ -203,6 +203,7 @@
try {
ContentProviderResult[] result = super.applyBatch(operations);
db.setTransactionSuccessful();
+ reloadLauncherIfExternal();
return result;
} finally {
db.endTransaction();
@@ -217,6 +218,7 @@
int count = db.delete(args.table, args.where, args.args);
if (count > 0) notifyListeners();
+ reloadLauncherIfExternal();
return count;
}
@@ -229,6 +231,7 @@
int count = db.update(args.table, values, args.where, args.args);
if (count > 0) notifyListeners();
+ reloadLauncherIfExternal();
return count;
}
@@ -399,7 +402,7 @@
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
private AutoInstallsLayout createWorkspaceLoaderFromAppRestriction() {
// UserManager.getApplicationRestrictions() requires minSdkVersion >= 18
- if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
+ if (!Utilities.ATLEAST_JB_MR2) {
return null;
}
@@ -1098,10 +1101,6 @@
= 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);
@@ -1220,9 +1219,6 @@
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/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index f2c85a1..8a5804f 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -143,7 +143,7 @@
*
* @return The unique content URL for the specified row.
*/
- static Uri getContentUri(long id) {
+ public static Uri getContentUri(long id) {
return Uri.parse("content://" + ProviderConfig.AUTHORITY +
"/" + TABLE_NAME + "/" + id);
}
diff --git a/src/com/android/launcher3/LauncherStateTransitionAnimation.java b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
index 38ccfa7..0e20fab 100644
--- a/src/com/android/launcher3/LauncherStateTransitionAnimation.java
+++ b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
@@ -23,15 +23,17 @@
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;
@@ -80,13 +82,6 @@
public class LauncherStateTransitionAnimation {
/**
- * Callbacks made during the state transition
- */
- interface Callbacks {
- public void onStateTransitionHideSearchBar();
- }
-
- /**
* Private callbacks made during transition setup.
*/
static abstract class PrivateTransitionCallbacks {
@@ -111,12 +106,10 @@
public static final int SINGLE_FRAME_DELAY = 16;
@Thunk Launcher mLauncher;
- @Thunk Callbacks mCb;
- @Thunk AnimatorSet mStateAnimation;
+ @Thunk AnimatorSet mCurrentAnimation;
- public LauncherStateTransitionAnimation(Launcher l, Callbacks cb) {
+ public LauncherStateTransitionAnimation(Launcher l) {
mLauncher = l;
- mCb = cb;
}
/**
@@ -125,8 +118,8 @@
* @param startSearchAfterTransition Immediately starts app search after the transition to
* All Apps is completed.
*/
- public void startAnimationToAllApps(final boolean animated,
- final boolean startSearchAfterTransition) {
+ public void startAnimationToAllApps(final Workspace.State fromWorkspaceState,
+ final boolean animated, final boolean startSearchAfterTransition) {
final AllAppsContainerView toView = mLauncher.getAppsView();
final View buttonView = mLauncher.getAllAppsButton();
PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() {
@@ -159,15 +152,16 @@
}
};
// Only animate the search bar if animating from spring loaded mode back to all apps
- startAnimationToOverlay(Workspace.State.NORMAL_HIDDEN, buttonView, toView,
- toView.getContentView(), toView.getRevealView(), toView.getSearchBarView(),
- animated, true /* hideSearchBar */, cb);
+ mCurrentAnimation = startAnimationToOverlay(fromWorkspaceState,
+ Workspace.State.NORMAL_HIDDEN, buttonView, toView, toView.getContentView(),
+ toView.getRevealView(), toView.getSearchBarView(), animated, cb);
}
/**
* Starts an animation to the widgets view.
*/
- public void startAnimationToWidgets(final boolean animated) {
+ public void startAnimationToWidgets(final Workspace.State fromWorkspaceState,
+ final boolean animated) {
final WidgetsContainerView toView = mLauncher.getWidgetsView();
final View buttonView = mLauncher.getWidgetsButton();
PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() {
@@ -176,17 +170,17 @@
return 0.3f;
}
};
- startAnimationToOverlay(Workspace.State.OVERVIEW_HIDDEN, buttonView, toView,
- toView.getContentView(), toView.getRevealView(), null, animated,
- true /* hideSearchBar */, cb);
+ mCurrentAnimation = startAnimationToOverlay(fromWorkspaceState,
+ Workspace.State.OVERVIEW_HIDDEN, buttonView, toView, toView.getContentView(),
+ toView.getRevealView(), null, animated, cb);
}
/**
* Starts and animation to the workspace from the current overlay view.
*/
public void startAnimationToWorkspace(final Launcher.State fromState,
- final Workspace.State toWorkspaceState,
- final boolean animated, final Runnable onCompleteRunnable) {
+ final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState,
+ final boolean animated, final Runnable onCompleteRunnable) {
if (toWorkspaceState != Workspace.State.NORMAL &&
toWorkspaceState != Workspace.State.SPRING_LOADED &&
toWorkspaceState != Workspace.State.OVERVIEW) {
@@ -194,9 +188,11 @@
}
if (fromState == Launcher.State.APPS || fromState == Launcher.State.APPS_SPRING_LOADED) {
- startAnimationToWorkspaceFromAllApps(toWorkspaceState, animated, onCompleteRunnable);
+ startAnimationToWorkspaceFromAllApps(fromWorkspaceState, toWorkspaceState,
+ animated, onCompleteRunnable);
} else {
- startAnimationToWorkspaceFromWidgets(toWorkspaceState, animated, onCompleteRunnable);
+ startAnimationToWorkspaceFromWidgets(fromWorkspaceState, toWorkspaceState,
+ animated, onCompleteRunnable);
}
}
@@ -204,12 +200,13 @@
* Creates and starts a new animation to a particular overlay view.
*/
@SuppressLint("NewApi")
- private void startAnimationToOverlay(final Workspace.State toWorkspaceState,
- final View buttonView, final View toView, final View contentView, final View revealView,
- final View overlaySearchBarView, final boolean animated, final boolean hideSearchBar,
- final PrivateTransitionCallbacks pCb) {
+ private AnimatorSet startAnimationToOverlay(final Workspace.State fromWorkspaceState,
+ final Workspace.State toWorkspaceState, final View buttonView, final View toView,
+ final View contentView, final View revealView, final View overlaySearchBarView,
+ final boolean animated, final PrivateTransitionCallbacks pCb) {
+ final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
final Resources res = mLauncher.getResources();
- final boolean material = Utilities.isLmpOrAbove();
+ final boolean material = Utilities.ATLEAST_LOLLIPOP;
final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime);
final int itemsAlphaStagger =
res.getInteger(R.integer.config_overlayItemsAlphaStagger);
@@ -227,11 +224,13 @@
// Create the workspace animation.
// NOTE: this call apparently also sets the state for the workspace if !animated
Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState,
- animated, overlaySearchBarView != null /* hasOverlaySearchBar */, layerViews);
+ animated, layerViews);
+
+ // Animate the search bar
+ startWorkspaceSearchBarAnimation(animation, fromWorkspaceState, toWorkspaceState,
+ animated ? revealDuration : 0, overlaySearchBarView);
if (animated && initialized) {
- mStateAnimation = LauncherAnimUtils.createAnimatorSet();
-
// Setup the reveal view animation
int width = revealView.getMeasuredWidth();
int height = revealView.getMeasuredHeight();
@@ -259,11 +258,11 @@
// 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);
@@ -271,7 +270,7 @@
// Play the animation
layerViews.put(revealView, BUILD_AND_SET_LAYER);
- mStateAnimation.play(panelAlphaAndDrift);
+ animation.play(panelAlphaAndDrift);
if (overlaySearchBarView != null) {
overlaySearchBarView.setAlpha(0f);
@@ -279,7 +278,7 @@
searchBarAlpha.setDuration(100);
searchBarAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
layerViews.put(overlaySearchBarView, BUILD_AND_SET_LAYER);
- mStateAnimation.play(searchBarAlpha);
+ animation.play(searchBarAlpha);
}
// Setup the animation for the content view
@@ -294,13 +293,13 @@
pageDrift.setDuration(revealDuration);
pageDrift.setInterpolator(new LogDecelerateInterpolator(100, 0));
pageDrift.setStartDelay(itemsAlphaStagger);
- mStateAnimation.play(pageDrift);
+ animation.play(pageDrift);
ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 0f, 1f);
itemsAlpha.setDuration(revealDuration);
itemsAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
itemsAlpha.setStartDelay(itemsAlphaStagger);
- mStateAnimation.play(itemsAlpha);
+ animation.play(itemsAlpha);
if (material) {
float startRadius = pCb.getMaterialRevealViewStartFinalRadius();
@@ -313,10 +312,10 @@
if (listener != null) {
reveal.addListener(listener);
}
- mStateAnimation.play(reveal);
+ animation.play(reveal);
}
- mStateAnimation.addListener(new AnimatorListenerAdapter() {
+ animation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
dispatchOnLauncherTransitionEnd(fromView, animated, false);
@@ -332,12 +331,8 @@
}
}
- if (hideSearchBar) {
- mCb.onStateTransitionHideSearchBar();
- }
-
// This can hold unnecessary references to views.
- mStateAnimation = null;
+ cleanupAnimation();
pCb.onTransitionComplete();
}
@@ -345,7 +340,7 @@
// Play the workspace animation
if (workspaceAnim != null) {
- mStateAnimation.play(workspaceAnim);
+ animation.play(workspaceAnim);
}
// Dispatch the prepare transition signal
@@ -353,23 +348,22 @@
dispatchOnLauncherTransitionPrepare(toView, animated, false);
- final AnimatorSet stateAnimation = mStateAnimation;
+ final AnimatorSet stateAnimation = animation;
final Runnable startAnimRunnable = new Runnable() {
public void run() {
- // Check that mStateAnimation hasn't changed while
+ // Check that mCurrentAnimation hasn't changed while
// we waited for a layout/draw pass
- if (mStateAnimation != stateAnimation)
+ if (mCurrentAnimation != stateAnimation)
return;
dispatchOnLauncherTransitionStart(fromView, animated, false);
dispatchOnLauncherTransitionStart(toView, animated, false);
// Enable all necessary layers
- boolean isLmpOrAbove = Utilities.isLmpOrAbove();
for (View v : layerViews.keySet()) {
if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
}
- if (isLmpOrAbove && Utilities.isViewAttachedToWindow(v)) {
+ if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) {
v.buildLayer();
}
}
@@ -377,12 +371,14 @@
// Focus the new view
toView.requestFocus();
- mStateAnimation.start();
+ stateAnimation.start();
}
};
toView.bringToFront();
toView.setVisibility(View.VISIBLE);
toView.post(startAnimRunnable);
+
+ return animation;
} else {
toView.setTranslationX(0.0f);
toView.setTranslationY(0.0f);
@@ -394,10 +390,6 @@
// Show the content view
contentView.setVisibility(View.VISIBLE);
- if (hideSearchBar) {
- mCb.onStateTransitionHideSearchBar();
- }
-
dispatchOnLauncherTransitionPrepare(fromView, animated, false);
dispatchOnLauncherTransitionStart(fromView, animated, false);
dispatchOnLauncherTransitionEnd(fromView, animated, false);
@@ -405,18 +397,19 @@
dispatchOnLauncherTransitionStart(toView, animated, false);
dispatchOnLauncherTransitionEnd(toView, animated, false);
pCb.onTransitionComplete();
+
+ return null;
}
}
/**
* Starts and animation to the workspace from the apps view.
*/
- private void startAnimationToWorkspaceFromAllApps(final Workspace.State toWorkspaceState,
- final boolean animated, final Runnable onCompleteRunnable) {
+ private void startAnimationToWorkspaceFromAllApps(final Workspace.State fromWorkspaceState,
+ final Workspace.State toWorkspaceState, final boolean animated,
+ final Runnable onCompleteRunnable) {
AllAppsContainerView appsView = mLauncher.getAppsView();
PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() {
- int[] mAllAppsToPanelDelta;
-
@Override
float getMaterialRevealViewFinalAlpha(View revealView) {
// No alpha anim from all apps
@@ -448,16 +441,18 @@
}
};
// Only animate the search bar if animating to spring loaded mode from all apps
- startAnimationToWorkspaceFromOverlay(toWorkspaceState, mLauncher.getAllAppsButton(),
- appsView, appsView.getContentView(), appsView.getRevealView(),
- appsView.getSearchBarView(), animated, onCompleteRunnable, cb);
+ mCurrentAnimation = startAnimationToWorkspaceFromOverlay(fromWorkspaceState, toWorkspaceState,
+ mLauncher.getAllAppsButton(), appsView, appsView.getContentView(),
+ appsView.getRevealView(), appsView.getSearchBarView(), animated,
+ onCompleteRunnable, cb);
}
/**
* Starts and animation to the workspace from the widgets view.
*/
- private void startAnimationToWorkspaceFromWidgets(final Workspace.State toWorkspaceState,
- final boolean animated, final Runnable onCompleteRunnable) {
+ private void startAnimationToWorkspaceFromWidgets(final Workspace.State fromWorkspaceState,
+ final Workspace.State toWorkspaceState, final boolean animated,
+ final Runnable onCompleteRunnable) {
final WidgetsContainerView widgetsView = mLauncher.getWidgetsView();
PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() {
@Override
@@ -475,20 +470,23 @@
};
}
};
- startAnimationToWorkspaceFromOverlay(toWorkspaceState, mLauncher.getWidgetsButton(),
- widgetsView, widgetsView.getContentView(), widgetsView.getRevealView(), null,
- animated, onCompleteRunnable, cb);
- }
+ mCurrentAnimation = startAnimationToWorkspaceFromOverlay(fromWorkspaceState,
+ toWorkspaceState, mLauncher.getWidgetsButton(), widgetsView,
+ widgetsView.getContentView(), widgetsView.getRevealView(), null, animated,
+ onCompleteRunnable, cb);
+ }
/**
* Creates and starts a new animation to the workspace.
*/
- private void startAnimationToWorkspaceFromOverlay(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) {
+ private AnimatorSet startAnimationToWorkspaceFromOverlay(final Workspace.State fromWorkspaceState,
+ 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) {
+ final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
final Resources res = mLauncher.getResources();
- final boolean material = Utilities.isLmpOrAbove();
+ final boolean material = Utilities.ATLEAST_LOLLIPOP;
final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime);
final int itemsAlphaStagger =
res.getInteger(R.integer.config_overlayItemsAlphaStagger);
@@ -506,15 +504,16 @@
// Create the workspace animation.
// NOTE: this call apparently also sets the state for the workspace if !animated
Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState,
- animated, overlaySearchBarView != null /* hasOverlaySearchBar */,
- layerViews);
+ animated, layerViews);
+
+ // Animate the search bar
+ startWorkspaceSearchBarAnimation(animation, fromWorkspaceState, toWorkspaceState,
+ animated ? revealDuration : 0, overlaySearchBarView);
if (animated && initialized) {
- mStateAnimation = LauncherAnimUtils.createAnimatorSet();
-
// Play the workspace animation
if (workspaceAnim != null) {
- mStateAnimation.play(workspaceAnim);
+ animation.play(workspaceAnim);
}
// hideAppsCustomizeHelper is called in some cases when it is already hidden
@@ -553,14 +552,14 @@
panelDriftY.setDuration(revealDuration - SINGLE_FRAME_DELAY);
panelDriftY.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
panelDriftY.setInterpolator(decelerateInterpolator);
- mStateAnimation.play(panelDriftY);
+ animation.play(panelDriftY);
ObjectAnimator panelDriftX = ObjectAnimator.ofFloat(revealView, "translationX",
0, revealViewToXDrift);
panelDriftX.setDuration(revealDuration - SINGLE_FRAME_DELAY);
panelDriftX.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
panelDriftX.setInterpolator(decelerateInterpolator);
- mStateAnimation.play(panelDriftX);
+ animation.play(panelDriftX);
// Setup animation for the reveal panel alpha
final float revealViewToAlpha = !material ? 0f :
@@ -571,7 +570,7 @@
panelAlpha.setDuration(material ? revealDuration : 150);
panelAlpha.setStartDelay(material ? 0 : itemsAlphaStagger + SINGLE_FRAME_DELAY);
panelAlpha.setInterpolator(decelerateInterpolator);
- mStateAnimation.play(panelAlpha);
+ animation.play(panelAlpha);
}
// Setup the animation for the content view
@@ -584,13 +583,13 @@
pageDrift.setDuration(revealDuration - SINGLE_FRAME_DELAY);
pageDrift.setInterpolator(decelerateInterpolator);
pageDrift.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
- mStateAnimation.play(pageDrift);
+ animation.play(pageDrift);
contentView.setAlpha(1f);
ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 1f, 0f);
itemsAlpha.setDuration(100);
itemsAlpha.setInterpolator(decelerateInterpolator);
- mStateAnimation.play(itemsAlpha);
+ animation.play(itemsAlpha);
if (overlaySearchBarView != null) {
overlaySearchBarView.setAlpha(1f);
@@ -599,7 +598,7 @@
searchAlpha.setInterpolator(decelerateInterpolator);
searchAlpha.setStartDelay(material ? 0 : itemsAlphaStagger + SINGLE_FRAME_DELAY);
layerViews.put(overlaySearchBarView, BUILD_AND_SET_LAYER);
- mStateAnimation.play(searchAlpha);
+ animation.play(searchAlpha);
}
if (material) {
@@ -615,14 +614,14 @@
if (listener != null) {
reveal.addListener(listener);
}
- mStateAnimation.play(reveal);
+ animation.play(reveal);
}
dispatchOnLauncherTransitionPrepare(fromView, animated, true);
dispatchOnLauncherTransitionPrepare(toView, animated, true);
}
- mStateAnimation.addListener(new AnimatorListenerAdapter() {
+ animation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
fromView.setVisibility(View.GONE);
@@ -652,35 +651,38 @@
}
// This can hold unnecessary references to views.
- mStateAnimation = null;
+ cleanupAnimation();
pCb.onTransitionComplete();
}
});
- final AnimatorSet stateAnimation = mStateAnimation;
+ final AnimatorSet stateAnimation = animation;
final Runnable startAnimRunnable = new Runnable() {
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
public void run() {
- // Check that mStateAnimation hasn't changed while
+ // Check that mCurrentAnimation hasn't changed while
// we waited for a layout/draw pass
- if (mStateAnimation != stateAnimation)
+ if (mCurrentAnimation != stateAnimation)
return;
+
dispatchOnLauncherTransitionStart(fromView, animated, false);
dispatchOnLauncherTransitionStart(toView, animated, false);
// Enable all necessary layers
- boolean isLmpOrAbove = Utilities.isLmpOrAbove();
for (View v : layerViews.keySet()) {
if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
}
- if (isLmpOrAbove && Utilities.isViewAttachedToWindow(v)) {
+ if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) {
v.buildLayer();
}
}
- mStateAnimation.start();
+ stateAnimation.start();
}
};
fromView.post(startAnimRunnable);
+
+ return animation;
} else {
fromView.setVisibility(View.GONE);
dispatchOnLauncherTransitionPrepare(fromView, animated, true);
@@ -695,9 +697,44 @@
if (onCompleteRunnable != null) {
onCompleteRunnable.run();
}
+
+ return null;
}
}
+ /**
+ * Coordinates the workspace search bar animation along with the launcher state animation.
+ */
+ private void startWorkspaceSearchBarAnimation(AnimatorSet animation,
+ final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState, int duration,
+ View overlaySearchBar) {
+ final SearchDropTargetBar.State toSearchBarState =
+ toWorkspaceState.getSearchDropTargetBarState();
+
+ if (overlaySearchBar != null) {
+ if ((toWorkspaceState == Workspace.State.NORMAL) &&
+ (fromWorkspaceState == Workspace.State.NORMAL_HIDDEN)) {
+ // If we are transitioning from the overlay to the workspace, then show the
+ // workspace search bar immediately and let the overlay search bar fade out on top
+ mLauncher.getSearchDropTargetBar().animateToState(toSearchBarState, 0);
+ } else if (fromWorkspaceState == Workspace.State.NORMAL) {
+ // If we are transitioning from the workspace to the overlay, then keep the
+ // workspace search bar visible until the overlay search bar fades in on top
+ animation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mLauncher.getSearchDropTargetBar().animateToState(toSearchBarState, 0);
+ }
+ });
+ } else {
+ // Otherwise, then just animate the workspace search bar normally
+ mLauncher.getSearchDropTargetBar().animateToState(toSearchBarState, duration);
+ }
+ } else {
+ // If there is no overlay search bar, then just animate the workspace search bar
+ mLauncher.getSearchDropTargetBar().animateToState(toSearchBarState, duration);
+ }
+ }
/**
* Dispatches the prepare-transition event to suitable views.
@@ -748,10 +785,14 @@
* Cancels the current animation.
*/
private void cancelAnimation() {
- if (mStateAnimation != null) {
- mStateAnimation.setDuration(0);
- mStateAnimation.cancel();
- mStateAnimation = null;
+ if (mCurrentAnimation != null) {
+ mCurrentAnimation.setDuration(0);
+ mCurrentAnimation.cancel();
+ mCurrentAnimation = null;
}
}
+
+ @Thunk void cleanupAnimation() {
+ mCurrentAnimation = null;
+ }
}
diff --git a/src/com/android/launcher3/LauncherViewPropertyAnimator.java b/src/com/android/launcher3/LauncherViewPropertyAnimator.java
index 4cafbbf..4406a2c 100644
--- a/src/com/android/launcher3/LauncherViewPropertyAnimator.java
+++ b/src/com/android/launcher3/LauncherViewPropertyAnimator.java
@@ -26,6 +26,7 @@
import java.util.EnumSet;
public class LauncherViewPropertyAnimator extends Animator implements AnimatorListener {
+
enum Properties {
TRANSLATION_X,
TRANSLATION_Y,
@@ -51,13 +52,12 @@
long mStartDelay;
long mDuration;
TimeInterpolator mInterpolator;
- ArrayList<Animator.AnimatorListener> mListeners;
+ ArrayList<Animator.AnimatorListener> mListeners = new ArrayList<>();
boolean mRunning = false;
FirstFrameAnimatorHelper mFirstFrameHelper;
public LauncherViewPropertyAnimator(View target) {
mTarget = target;
- mListeners = new ArrayList<Animator.AnimatorListener>();
}
@Override
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index c9bbe0a..28e3367 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -18,7 +18,6 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
import android.animation.LayoutTransition;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
@@ -64,7 +63,7 @@
// 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;
private static final float RETURN_TO_ORIGINAL_PAGE_THRESHOLD = 0.33f;
@@ -180,7 +179,7 @@
private Runnable mPostReorderingPreZoomInRunnable;
// Convenience/caching
- protected static final Matrix sTmpInvMatrix = new Matrix();
+ private static final Matrix sTmpInvMatrix = new Matrix();
private static final float[] sTmpPoint = new float[2];
private static final int[] sTmpIntPoint = new int[2];
private static final Rect sTmpRect = new Rect();
@@ -382,7 +381,7 @@
/**
* Returns the index of page to be shown immediately afterwards.
*/
- int getNextPage() {
+ public int getNextPage() {
return (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
}
@@ -1681,16 +1680,14 @@
// 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);
}
@@ -2140,13 +2137,12 @@
// 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) {
@@ -2243,8 +2239,6 @@
animateDragViewToOriginalPosition();
}
- private static final int ANIM_TAG_KEY = 100;
-
/* Accessibility */
@SuppressWarnings("deprecation")
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@@ -2264,7 +2258,7 @@
// Besides disabling the accessibility long-click, this also prevents this view from getting
// accessibility focus.
info.setLongClickable(false);
- if (Utilities.isLmpOrAbove()) {
+ if (Utilities.ATLEAST_LOLLIPOP) {
info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK);
}
}
diff --git a/src/com/android/launcher3/PendingAppWidgetHostView.java b/src/com/android/launcher3/PendingAppWidgetHostView.java
index 08f8e56..40eadab 100644
--- a/src/com/android/launcher3/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/PendingAppWidgetHostView.java
@@ -16,13 +16,17 @@
package com.android.launcher3;
+import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources.Theme;
import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.os.Build;
import android.os.Bundle;
import android.text.Layout;
import android.text.StaticLayout;
@@ -32,6 +36,8 @@
import android.view.View.OnClickListener;
public class PendingAppWidgetHostView extends LauncherAppWidgetHostView implements OnClickListener {
+ private static final float SETUP_ICON_SIZE_FACTOR = 2f / 5;
+ private static final float MIN_SATUNATION = 0.7f;
private static Theme sPreloaderTheme;
@@ -47,13 +53,14 @@
private Bitmap mIcon;
private Drawable mCenterDrawable;
- private Drawable mTopCornerDrawable;
+ private Drawable mSettingIconDrawable;
private boolean mDrawableSizeChanged;
private final TextPaint mPaint;
private Layout mSetupTextLayout;
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
public PendingAppWidgetHostView(Context context, LauncherAppWidgetInfo info,
boolean disabledForSafeMode) {
super(context);
@@ -70,6 +77,10 @@
mLauncher.getDeviceProfile().iconTextSizePx, getResources().getDisplayMetrics()));
setBackgroundResource(R.drawable.quantum_panel_dark);
setWillNotDraw(false);
+
+ if (Utilities.ATLEAST_LOLLIPOP) {
+ setElevation(getResources().getDimension(R.dimen.pending_widget_elevation));
+ }
}
@Override
@@ -124,10 +135,12 @@
FastBitmapDrawable disabledIcon = mLauncher.createIconDrawable(mIcon);
disabledIcon.setGhostModeEnabled(true);
mCenterDrawable = disabledIcon;
- mTopCornerDrawable = null;
+ mSettingIconDrawable = null;
} else if (isReadyForClickSetup()) {
- mCenterDrawable = getResources().getDrawable(R.drawable.ic_setting);
- mTopCornerDrawable = new FastBitmapDrawable(mIcon);
+ mCenterDrawable = new FastBitmapDrawable(mIcon);
+ mSettingIconDrawable = getResources().getDrawable(R.drawable.ic_setting).mutate();
+
+ updateSettingColor();
} else {
if (sPreloaderTheme == null) {
sPreloaderTheme = getResources().newTheme();
@@ -137,13 +150,25 @@
FastBitmapDrawable drawable = mLauncher.createIconDrawable(mIcon);
mCenterDrawable = new PreloadIconDrawable(drawable, sPreloaderTheme);
mCenterDrawable.setCallback(this);
- mTopCornerDrawable = null;
+ mSettingIconDrawable = null;
applyState();
}
mDrawableSizeChanged = true;
}
}
+ private void updateSettingColor() {
+ int color = Utilities.findDominantColorByHue(mIcon, 20);
+ // Make the dominant color bright.
+ float[] hsv = new float[3];
+ Color.colorToHSV(color, hsv);
+ hsv[1] = Math.min(hsv[1], MIN_SATUNATION);
+ hsv[2] = 1;
+ color = Color.HSVToColor(hsv);
+
+ mSettingIconDrawable.setColorFilter(color, PorterDuff.Mode.SRC_IN);
+ }
+
@Override
protected boolean verifyDrawable(Drawable who) {
return (who == mCenterDrawable) || super.verifyDrawable(who);
@@ -169,6 +194,83 @@
&& (mInfo.restoreStatus & LauncherAppWidgetInfo.FLAG_UI_NOT_READY) != 0;
}
+ private void updateDrawableBounds() {
+ DeviceProfile grid = mLauncher.getDeviceProfile();
+ int paddingTop = getPaddingTop();
+ int paddingBottom = getPaddingBottom();
+ int paddingLeft = getPaddingLeft();
+ int paddingRight = getPaddingRight();
+
+ int minPadding = getResources()
+ .getDimensionPixelSize(R.dimen.pending_widget_min_padding);
+
+ int availableWidth = getWidth() - paddingLeft - paddingRight - 2 * minPadding;
+ int availableHeight = getHeight() - paddingTop - paddingBottom - 2 * minPadding;
+
+ if (mSettingIconDrawable == null) {
+ int outset = (mCenterDrawable instanceof PreloadIconDrawable) ?
+ ((PreloadIconDrawable) mCenterDrawable).getOutset() : 0;
+ int maxSize = grid.iconSizePx + 2 * outset;
+ int size = Math.min(maxSize, Math.min(availableWidth, availableHeight));
+
+ mRect.set(0, 0, size, size);
+ mRect.inset(outset, outset);
+ mRect.offsetTo((getWidth() - mRect.width()) / 2, (getHeight() - mRect.height()) / 2);
+ mCenterDrawable.setBounds(mRect);
+ } else {
+ float iconSize = Math.min(availableWidth, availableHeight);
+
+ // Use twice the setting size factor, as the setting is drawn at a corner and the
+ // icon is drawn in the center.
+ float settingIconScaleFactor = 1 + SETUP_ICON_SIZE_FACTOR * 2;
+ int maxSize = Math.max(availableWidth, availableHeight);
+ if (iconSize * settingIconScaleFactor > maxSize) {
+ // There is an overlap
+ iconSize = maxSize / settingIconScaleFactor;
+ }
+
+ int actualIconSize = (int) Math.min(iconSize, grid.iconSizePx);
+
+ // Recreate the setup text.
+ mSetupTextLayout = new StaticLayout(
+ getResources().getText(R.string.gadget_setup_text), mPaint, availableWidth,
+ Layout.Alignment.ALIGN_CENTER, 1, 0, true);
+ int textHeight = mSetupTextLayout.getHeight();
+
+ // Extra icon size due to the setting icon
+ float minHeightWithText = textHeight + actualIconSize * settingIconScaleFactor
+ + grid.iconDrawablePaddingPx;
+
+ int iconTop;
+ if (minHeightWithText < availableHeight) {
+ // We can draw the text as well
+ iconTop = (getHeight() - textHeight -
+ grid.iconDrawablePaddingPx - actualIconSize) / 2;
+
+ } else {
+ // The text will not fit. Only draw the icons.
+ iconTop = (getHeight() - actualIconSize) / 2;
+ mSetupTextLayout = null;
+ }
+
+ mRect.set(0, 0, actualIconSize, actualIconSize);
+ mRect.offset((getWidth() - actualIconSize) / 2, iconTop);
+ mCenterDrawable.setBounds(mRect);
+
+ mRect.left = paddingLeft + minPadding;
+ mRect.right = mRect.left + (int) (SETUP_ICON_SIZE_FACTOR * actualIconSize);
+ mRect.top = paddingTop + minPadding;
+ mRect.bottom = mRect.top + (int) (SETUP_ICON_SIZE_FACTOR * actualIconSize);
+ mSettingIconDrawable.setBounds(mRect);
+
+ if (mSetupTextLayout != null) {
+ // Set up position for dragging the text
+ mRect.left = paddingLeft + minPadding;
+ mRect.top = mCenterDrawable.getBounds().bottom + grid.iconDrawablePaddingPx;
+ }
+ }
+ }
+
@Override
protected void onDraw(Canvas canvas) {
if (mCenterDrawable == null) {
@@ -176,81 +278,21 @@
return;
}
- DeviceProfile grid = mLauncher.getDeviceProfile();
- if (mTopCornerDrawable == null) {
- if (mDrawableSizeChanged) {
- int outset = (mCenterDrawable instanceof PreloadIconDrawable) ?
- ((PreloadIconDrawable) mCenterDrawable).getOutset() : 0;
- int maxSize = grid.iconSizePx + 2 * outset;
- int size = Math.min(maxSize, Math.min(
- getWidth() - getPaddingLeft() - getPaddingRight(),
- getHeight() - getPaddingTop() - getPaddingBottom()));
-
- mRect.set(0, 0, size, size);
- mRect.inset(outset, outset);
- mRect.offsetTo((getWidth() - mRect.width()) / 2, (getHeight() - mRect.height()) / 2);
- mCenterDrawable.setBounds(mRect);
- mDrawableSizeChanged = false;
- }
- mCenterDrawable.draw(canvas);
- } else {
- // Draw the top corner icon and "Setup" text is possible
- if (mDrawableSizeChanged) {
- int iconSize = grid.iconSizePx;
- int paddingTop = getPaddingTop();
- int paddingBottom = getPaddingBottom();
- int paddingLeft = getPaddingLeft();
- int paddingRight = getPaddingRight();
-
- int availableWidth = getWidth() - paddingLeft - paddingRight;
- int availableHeight = getHeight() - paddingTop - paddingBottom;
-
- // Recreate the setup text.
- mSetupTextLayout = new StaticLayout(
- getResources().getText(R.string.gadget_setup_text), mPaint, availableWidth,
- Layout.Alignment.ALIGN_CENTER, 1, 0, true);
- if (mSetupTextLayout.getLineCount() == 1) {
- // The text fits in a single line. No need to draw the setup icon.
- int size = Math.min(iconSize, Math.min(availableWidth,
- availableHeight - mSetupTextLayout.getHeight()));
- mRect.set(0, 0, size, size);
- mRect.offsetTo((getWidth() - mRect.width()) / 2,
- (getHeight() - mRect.height() - mSetupTextLayout.getHeight()
- - grid.iconDrawablePaddingPx) / 2);
-
- mTopCornerDrawable.setBounds(mRect);
-
- // Update left and top to indicate the position where the text will be drawn.
- mRect.left = paddingLeft;
- mRect.top = mRect.bottom + grid.iconDrawablePaddingPx;
- } else {
- // The text can't be drawn in a single line. Draw a setup icon instead.
- mSetupTextLayout = null;
- int size = Math.min(iconSize, Math.min(
- getWidth() - paddingLeft - paddingRight,
- getHeight() - paddingTop - paddingBottom));
- mRect.set(0, 0, size, size);
- mRect.offsetTo((getWidth() - mRect.width()) / 2, (getHeight() - mRect.height()) / 2);
- mCenterDrawable.setBounds(mRect);
-
- size = Math.min(size / 2,
- Math.max(mRect.top - paddingTop, mRect.left - paddingLeft));
- mTopCornerDrawable.setBounds(paddingLeft, paddingTop,
- paddingLeft + size, paddingTop + size);
- }
- mDrawableSizeChanged = false;
- }
-
- if (mSetupTextLayout == null) {
- mCenterDrawable.draw(canvas);
- mTopCornerDrawable.draw(canvas);
- } else {
- canvas.save();
- canvas.translate(mRect.left, mRect.top);
- mSetupTextLayout.draw(canvas);
- canvas.restore();
- mTopCornerDrawable.draw(canvas);
- }
+ if (mDrawableSizeChanged) {
+ updateDrawableBounds();
+ mDrawableSizeChanged = false;
}
+
+ mCenterDrawable.draw(canvas);
+ if (mSettingIconDrawable != null) {
+ mSettingIconDrawable.draw(canvas);
+ }
+ if (mSetupTextLayout != null) {
+ canvas.save();
+ canvas.translate(mRect.left, mRect.top);
+ mSetupTextLayout.draw(canvas);
+ canvas.restore();
+ }
+
}
}
diff --git a/src/com/android/launcher3/PreloadIconDrawable.java b/src/com/android/launcher3/PreloadIconDrawable.java
index bcb59c4..45e4b2c 100644
--- a/src/com/android/launcher3/PreloadIconDrawable.java
+++ b/src/com/android/launcher3/PreloadIconDrawable.java
@@ -213,6 +213,10 @@
return mAnimationProgress;
}
+ public boolean hasNotCompleted() {
+ return mAnimationProgress < ANIMATION_PROGRESS_COMPLETED;
+ }
+
@Override
public int getIntrinsicHeight() {
return mIcon.getIntrinsicHeight();
diff --git a/src/com/android/launcher3/SearchDropTargetBar.java b/src/com/android/launcher3/SearchDropTargetBar.java
index 7c8f441..fdcad82 100644
--- a/src/com/android/launcher3/SearchDropTargetBar.java
+++ b/src/com/android/launcher3/SearchDropTargetBar.java
@@ -18,32 +18,58 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
import android.content.Context;
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.
*/
public class SearchDropTargetBar extends FrameLayout implements DragController.DragListener {
- private static final int TRANSITION_DURATION = 200;
+ /** The different states that the search bar space can be in. */
+ public enum State {
+ INVISIBLE (0f, 0f),
+ SEARCH_BAR (1f, 0f),
+ DROP_TARGET (0f, 1f);
- private ObjectAnimator mShowDropTargetBarAnim;
- private ValueAnimator mHideSearchBarAnim;
+ private final float mSearchBarAlpha;
+ private final float mDropTargetBarAlpha;
+
+ State(float sbAlpha, float dtbAlpha) {
+ mSearchBarAlpha = sbAlpha;
+ mDropTargetBarAlpha = dtbAlpha;
+ }
+
+ float getSearchBarAlpha() {
+ return mSearchBarAlpha;
+ }
+
+ float getDropTargetBarAlpha() {
+ return mDropTargetBarAlpha;
+ }
+ }
+
+ private static int DEFAULT_DRAG_FADE_DURATION = 175;
+
+ private LauncherViewPropertyAnimator mDropTargetBarAnimator;
+ private LauncherViewPropertyAnimator mQSBSearchBarAnimator;
private static final AccelerateInterpolator sAccelerateInterpolator =
new AccelerateInterpolator();
- private boolean mIsSearchBarHidden;
- private View mQSBSearchBar;
- private View mDropTargetBar;
+ 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;
@@ -75,39 +101,6 @@
mUninstallDropTarget.setLauncher(launcher);
}
- public void setQsbSearchBar(View qsb) {
- mQSBSearchBar = qsb;
- if (mQSBSearchBar != null) {
- mHideSearchBarAnim = LauncherAnimUtils.ofFloat(mQSBSearchBar, "alpha", 1f, 0f);
- setupAnimation(mHideSearchBarAnim, mQSBSearchBar);
- } else {
- // Create a no-op animation of the search bar is null
- mHideSearchBarAnim = ValueAnimator.ofFloat(0, 0);
- mHideSearchBarAnim.setDuration(TRANSITION_DURATION);
- }
- }
-
- private void prepareStartAnimation(View v) {
- // Enable the hw layers before the animation starts (will be disabled in the onAnimationEnd
- // callback below)
- if (v != null) {
- v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
- }
- }
-
- private void setupAnimation(ValueAnimator anim, final View v) {
- anim.setInterpolator(sAccelerateInterpolator);
- anim.setDuration(TRANSITION_DURATION);
- anim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- if (v != null) {
- v.setLayerType(View.LAYER_TYPE_NONE, null);
- }
- }
- });
- }
-
@Override
protected void onFinishInflate() {
super.onFinishInflate();
@@ -124,72 +117,89 @@
// Create the various fade animations
mDropTargetBar.setAlpha(0f);
- mShowDropTargetBarAnim = LauncherAnimUtils.ofFloat(mDropTargetBar, "alpha", 0f, 1f);
- setupAnimation(mShowDropTargetBarAnim, mDropTargetBar);
+ 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);
+ }
+ }
+ });
}
- /**
- * Finishes all the animations on the search and drop target bars.
- */
- public void finishAnimations() {
- prepareStartAnimation(mDropTargetBar);
- mShowDropTargetBarAnim.reverse();
- prepareStartAnimation(mQSBSearchBar);
- mHideSearchBarAnim.reverse();
- }
+ public void setQsbSearchBar(View qsb) {
+ mQSB = qsb;
+ if (mQSB != null) {
+ // Update the search ber animation
+ mQSBSearchBarAnimator = new LauncherViewPropertyAnimator(mQSB);
+ mQSBSearchBarAnimator.setInterpolator(sAccelerateInterpolator);
+ mQSBSearchBarAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ // Ensure that the view is visible for the animation
+ if (mQSB != null) {
+ mQSB.setVisibility(View.VISIBLE);
+ }
+ }
- /**
- * Shows the search bar.
- */
- public void showSearchBar(boolean animated) {
- if (!mIsSearchBarHidden) return;
- if (animated) {
- prepareStartAnimation(mQSBSearchBar);
- mHideSearchBarAnim.reverse();
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (mQSB != null) {
+ AlphaUpdateListener.updateVisibility(mQSB, mAccessibilityEnabled);
+ }
+ }
+ });
} else {
- mHideSearchBarAnim.cancel();
- if (mQSBSearchBar != null) {
- mQSBSearchBar.setAlpha(1f);
+ mQSBSearchBarAnimator = null;
+ }
+ }
+
+ /**
+ * Animates the current search bar state to a new state. If the {@param duration} is 0, then
+ * the state is applied immediately.
+ */
+ public void animateToState(State newState, int duration) {
+ if (mState != newState) {
+ mState = newState;
+
+ // Update the accessibility state
+ AccessibilityManager am = (AccessibilityManager)
+ getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
+ mAccessibilityEnabled = am.isEnabled();
+
+ animateViewAlpha(mQSBSearchBarAnimator, mQSB, newState.getSearchBarAlpha(),
+ duration);
+ animateViewAlpha(mDropTargetBarAnimator, mDropTargetBar, newState.getDropTargetBarAlpha(),
+ duration);
+ }
+ }
+
+ /**
+ * 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);
}
}
- mIsSearchBarHidden = false;
- }
-
- /**
- * Hides the search bar. We only use this for clings.
- */
- public void hideSearchBar(boolean animated) {
- if (mIsSearchBarHidden) return;
- if (animated) {
- prepareStartAnimation(mQSBSearchBar);
- mHideSearchBarAnim.start();
- } else {
- mHideSearchBarAnim.cancel();
- if (mQSBSearchBar != null) {
- mQSBSearchBar.setAlpha(0f);
- }
- }
- mIsSearchBarHidden = true;
- }
-
- /**
- * Shows the drop target bar.
- */
- public void showDeleteTarget() {
- // Animate out the QSB search bar, and animate in the drop target bar
- prepareStartAnimation(mDropTargetBar);
- mShowDropTargetBarAnim.start();
- hideSearchBar(true);
- }
-
- /**
- * Hides the drop target bar.
- */
- public void hideDeleteTarget() {
- // Restore the QSB search bar, and animate out the drop target bar
- prepareStartAnimation(mDropTargetBar);
- mShowDropTargetBarAnim.reverse();
- showSearchBar(true);
}
/*
@@ -197,9 +207,13 @@
*/
@Override
public void onDragStart(DragSource source, ItemInfo info, int dragAction) {
- showDeleteTarget();
+ 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;
}
@@ -207,22 +221,25 @@
@Override
public void onDragEnd() {
if (!mDeferOnDragEnd) {
- hideDeleteTarget();
+ animateToState(State.SEARCH_BAR, DEFAULT_DRAG_FADE_DURATION);
} else {
mDeferOnDragEnd = false;
}
}
+ /**
+ * @return the bounds of the QSB search bar.
+ */
public Rect getSearchBarBounds() {
- if (mQSBSearchBar != null) {
+ if (mQSB != null) {
final int[] pos = new int[2];
- mQSBSearchBar.getLocationOnScreen(pos);
+ mQSB.getLocationOnScreen(pos);
final Rect rect = new Rect();
rect.left = pos[0];
rect.top = pos[1];
- rect.right = pos[0] + mQSBSearchBar.getWidth();
- rect.bottom = pos[1] + mQSBSearchBar.getHeight();
+ rect.right = pos[0] + mQSB.getWidth();
+ rect.bottom = pos[1] + mQSB.getHeight();
return rect;
} else {
return null;
@@ -230,8 +247,8 @@
}
public void enableAccessibleDrag(boolean enable) {
- if (mQSBSearchBar != null) {
- mQSBSearchBar.setVisibility(enable ? View.GONE : View.VISIBLE);
+ if (mQSB != null) {
+ mQSB.setVisibility(enable ? View.GONE : View.VISIBLE);
}
mInfoDropTarget.enableAccessibleDrag(enable);
mDeleteDropTarget.enableAccessibleDrag(enable);
diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
index 157b48a..20c2773 100644
--- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -18,12 +18,9 @@
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 @@
private int mHeightGap;
private int mCountX;
- private int mCountY;
private Launcher mLauncher;
@@ -62,7 +58,6 @@
mWidthGap = widthGap;
mHeightGap = heightGap;
mCountX = countX;
- mCountY = countY;
}
public View getChildAt(int x, int y) {
@@ -80,24 +75,6 @@
}
@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 @@
}
}
- @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 fe76634..a86a2f9 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -70,7 +70,7 @@
/**
* The intent used to start the application.
*/
- Intent intent;
+ public Intent intent;
/**
* Indicates whether the icon comes from an application's resource (if false)
@@ -197,13 +197,17 @@
return mIcon;
}
- public void updateIcon(IconCache iconCache) {
+ public void updateIcon(IconCache iconCache, boolean useLowRes) {
if (itemType == Favorites.ITEM_TYPE_APPLICATION) {
iconCache.getTitleAndIcon(this, promisedIntent != null ? promisedIntent : intent, user,
- shouldUseLowResIcon());
+ useLowRes);
}
}
+ public void updateIcon(IconCache iconCache) {
+ updateIcon(iconCache, shouldUseLowResIcon());
+ }
+
@Override
void onAddToDatabase(Context context, ContentValues values) {
super.onAddToDatabase(context, values);
diff --git a/src/com/android/launcher3/StartupReceiver.java b/src/com/android/launcher3/StartupReceiver.java
deleted file mode 100644
index 65f913f..0000000
--- 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 cb0e252..4aba150 100644
--- a/src/com/android/launcher3/Stats.java
+++ b/src/com/android/launcher3/Stats.java
@@ -25,6 +25,8 @@
import android.view.View;
import android.view.ViewParent;
+import com.android.launcher3.config.ProviderConfig;
+
public class Stats {
/**
@@ -71,7 +73,7 @@
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 da46e6a..54c5509 100644
--- a/src/com/android/launcher3/StylusEventHelper.java
+++ b/src/com/android/launcher3/StylusEventHelper.java
@@ -1,8 +1,5 @@
package com.android.launcher3;
-import com.android.launcher3.Utilities;
-
-import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
diff --git a/src/com/android/launcher3/UninstallDropTarget.java b/src/com/android/launcher3/UninstallDropTarget.java
index 7c548a5..9ed4fb6 100644
--- a/src/com/android/launcher3/UninstallDropTarget.java
+++ b/src/com/android/launcher3/UninstallDropTarget.java
@@ -38,7 +38,7 @@
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public static boolean supportsDrop(Context context, Object info) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+ if (Utilities.ATLEAST_JB_MR2) {
UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
Bundle restrictions = userManager.getUserRestrictions();
if (restrictions.getBoolean(UserManager.DISALLOW_APPS_CONTROL, false)
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 8fd298d..2340f95 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -56,8 +56,11 @@
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;
@@ -87,6 +90,24 @@
private static final int[] sLoc0 = new int[2];
private static final int[] sLoc1 = new int[2];
+ // TODO: use Build.VERSION_CODES when available
+ public static final boolean ATLEAST_MARSHMALLOW = Build.VERSION.SDK_INT >= 23;
+
+ public static final boolean ATLEAST_LOLLIPOP_MR1 =
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1;
+
+ public static final boolean ATLEAST_LOLLIPOP =
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
+
+ public static final boolean ATLEAST_KITKAT =
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
+
+ public static final boolean ATLEAST_JB_MR1 =
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1;
+
+ public static final boolean ATLEAST_JB_MR2 =
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2;
+
// To turn on these properties, type
// adb shell setprop log.tag.PROPERTY_NAME [VERBOSE | SUPPRESS]
private static final String FORCE_ENABLE_ROTATION_PROPERTY = "launcher_force_rotate";
@@ -110,21 +131,16 @@
return sForceEnableRotation || context.getResources().getBoolean(R.bool.allow_rotation);
}
- /**
- * Indicates if the device is running LMP or higher.
- */
- public static boolean isLmpOrAbove() {
- return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
- }
-
- public static boolean isLmpMR1OrAbove() {
- // TODO(adamcohen): update to Build.VERSION_CODES.LOLLIPOP_MR1 once building against 22;
- return Build.VERSION.SDK_INT >= 22;
- }
-
- public static boolean isLmpMR1() {
- // TODO(adamcohen): update to Build.VERSION_CODES.LOLLIPOP_MR1 once building against 22;
- return Build.VERSION.SDK_INT == 22;
+ 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) {
@@ -339,15 +355,6 @@
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);
@@ -368,11 +375,18 @@
}
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(
@@ -515,16 +529,6 @@
return null;
}
- @TargetApi(Build.VERSION_CODES.KITKAT)
- public static boolean isViewAttachedToWindow(View v) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.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.
@@ -544,7 +548,7 @@
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
for (AppWidgetProviderInfo info : appWidgetManager.getInstalledProviders()) {
if (info.provider.getPackageName().equals(providerPkg)) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ if (ATLEAST_JB_MR1) {
if ((info.widgetCategory & AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX) != 0) {
return info;
} else if (defaultWidgetForSearchPackage == null) {
@@ -655,12 +659,12 @@
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public static boolean isRtl(Resources res) {
- return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) &&
+ return ATLEAST_JB_MR1 &&
(res.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
}
public static void assertWorkerThread() {
- if (LauncherAppState.isDogfoodBuild() &&
+ if (ProviderConfig.IS_DOGFOOD_BUILD &&
(LauncherModel.sWorkerThread.getThreadId() != Process.myTid())) {
throw new IllegalStateException();
}
@@ -709,4 +713,18 @@
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;
+ }
+ }
}
diff --git a/src/com/android/launcher3/WallpaperChangedReceiver.java b/src/com/android/launcher3/WallpaperChangedReceiver.java
deleted file mode 100644
index 2d5612f..0000000
--- 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/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java
index 629387e..3460555 100644
--- a/src/com/android/launcher3/WidgetPreviewLoader.java
+++ b/src/com/android/launcher3/WidgetPreviewLoader.java
@@ -105,7 +105,7 @@
* sizes (landscape vs portrait).
*/
private static class CacheDb extends SQLiteOpenHelper {
- private static final int DB_VERSION = 3;
+ private static final int DB_VERSION = 4;
private static final String TABLE_NAME = "shortcut_and_widget_previews";
private static final String COLUMN_COMPONENT = "componentName";
@@ -212,7 +212,6 @@
public void removeObsoletePreviews(ArrayList<Object> list) {
Utilities.assertWorkerThread();
- LongSparseArray<UserHandleCompat> userIdCache = new LongSparseArray<>();
LongSparseArray<HashSet<String>> validPackages = new LongSparseArray<>();
for (Object obj : list) {
@@ -227,15 +226,7 @@
pkg = info.provider.getPackageName();
}
- int userIdIndex = userIdCache.indexOfValue(user);
- final long userId;
- if (userIdIndex < 0) {
- userId = mUserManager.getSerialNumberForUser(user);
- userIdCache.put(userId, user);
- } else {
- userId = userIdCache.keyAt(userIdIndex);
- }
-
+ final long userId = mUserManager.getSerialNumberForUser(user);
HashSet<String> packages = validPackages.get(userId);
if (packages == null) {
packages = new HashSet<>();
@@ -361,8 +352,8 @@
}
final boolean widgetPreviewExists = (drawable != null);
- final int spanX = info.getSpanX(launcher) < 1 ? 1 : info.getSpanX(launcher);
- final int spanY = info.getSpanY(launcher) < 1 ? 1 : info.getSpanY(launcher);
+ final int spanX = info.spanX;
+ final int spanY = info.spanY;
int previewWidth;
int previewHeight;
@@ -386,7 +377,7 @@
preScaledWidthOut[0] = previewWidth;
}
if (previewWidth > maxPreviewWidth) {
- scale = maxPreviewWidth / (float) previewWidth;
+ scale = (maxPreviewWidth - 2 * mProfileBadgeMargin) / (float) (previewWidth);
}
if (scale != 1f) {
previewWidth = (int) (scale * previewWidth);
@@ -405,7 +396,7 @@
}
// Draw the scaled preview into the final bitmap
- int x = (preview.getWidth() - previewWidth - mProfileBadgeMargin) / 2;
+ int x = (preview.getWidth() - previewWidth) / 2;
if (widgetPreviewExists) {
drawable.setBounds(x, 0, x + previewWidth, previewHeight);
drawable.draw(c);
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index edf8a32..964a83d 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -28,12 +28,10 @@
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.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.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;
@@ -136,9 +139,6 @@
private int mDragOverX = -1;
private int mDragOverY = -1;
- private static Rect mLandscapeCellLayoutMetrics = null;
- private static Rect mPortraitCellLayoutMetrics = null;
-
CustomContentCallbacks mCustomContentCallbacks;
boolean mCustomContentShowing;
private float mLastCustomContentScrollProgress = -1f;
@@ -177,7 +177,24 @@
// State variable that indicates whether the pages are small (ie when you're
// in all apps or customize mode)
- enum State { NORMAL, NORMAL_HIDDEN, SPRING_LOADED, OVERVIEW, OVERVIEW_HIDDEN};
+ enum State {
+ NORMAL (SearchDropTargetBar.State.SEARCH_BAR),
+ NORMAL_HIDDEN (SearchDropTargetBar.State.INVISIBLE),
+ SPRING_LOADED (SearchDropTargetBar.State.DROP_TARGET),
+ OVERVIEW (SearchDropTargetBar.State.INVISIBLE),
+ OVERVIEW_HIDDEN (SearchDropTargetBar.State.INVISIBLE);
+
+ private final SearchDropTargetBar.State mBarState;
+
+ State(SearchDropTargetBar.State searchBarState) {
+ mBarState = searchBarState;
+ }
+
+ public SearchDropTargetBar.State getSearchDropTargetBarState() {
+ return mBarState;
+ }
+ };
+
private State mState = State.NORMAL;
private boolean mIsSwitchingState = false;
@@ -300,8 +317,9 @@
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.Workspace, defStyle, 0);
mSpringLoadedShrinkFactor =
- res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f;
- mOverviewModeShrinkFactor = grid.getOverviewModeScale(mIsRtl);
+ res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f;
+ mOverviewModeShrinkFactor =
+ res.getInteger(R.integer.config_workspaceOverviewShrinkPercentage) / 100f;
mOriginalDefaultPage = mDefaultPage = a.getInt(R.styleable.Workspace_defaultScreen, 1);
a.recycle();
@@ -859,6 +877,9 @@
}
}
+ LauncherAccessibilityDelegate delegate =
+ LauncherAppState.getInstance().getAccessibilityDelegate();
+
// We enforce at least one page to add new items to. In the case that we remove the last
// such screen, we convert the last screen to the empty screen
int minScreens = 1 + numCustomPages();
@@ -873,6 +894,11 @@
if (indexOfChild(cl) < currentPage) {
pageShift++;
}
+
+ if (delegate != null && delegate.isInAccessibleDrag()) {
+ cl.enableAccessibleDrag(false, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG);
+ }
+
removeView(cl);
} else {
// if this is the last non-custom content screen, convert it to the empty screen
@@ -1282,12 +1308,12 @@
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);
@@ -1556,7 +1582,7 @@
// Reset our click listener
setOnClickListener(mLauncher);
}
- mLauncher.getSearchBar().enableAccessibleDrag(enable);
+ mLauncher.getSearchDropTargetBar().enableAccessibleDrag(enable);
mLauncher.getHotseat().getLayout()
.enableAccessibleDrag(enable, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG);
}
@@ -1956,15 +1982,17 @@
int getOverviewModeTranslationY() {
DeviceProfile grid = mLauncher.getDeviceProfile();
- Rect overviewBar = grid.getOverviewModeButtonBarRect();
+ Rect workspacePadding = grid.getWorkspacePadding(Utilities.isRtl(getResources()));
+ int overviewButtonBarHeight = grid.getOverviewModeButtonBarHeight();
- int availableHeight = getViewportHeight();
int scaledHeight = (int) (mOverviewModeShrinkFactor * getNormalChildHeight());
- int offsetFromTopEdge = (availableHeight - scaledHeight) / 2;
- int offsetToCenterInOverview = (availableHeight - mInsets.top - overviewBar.height()
- - scaledHeight) / 2;
-
- return -offsetFromTopEdge + mInsets.top + offsetToCenterInOverview;
+ int workspaceTop = mInsets.top + workspacePadding.top;
+ int workspaceBottom = getViewportHeight() - mInsets.bottom - workspacePadding.bottom;
+ int overviewTop = mInsets.top;
+ int overviewBottom = getViewportHeight() - mInsets.bottom - overviewButtonBarHeight;
+ int workspaceOffsetTopEdge = workspaceTop + ((workspaceBottom - workspaceTop) - scaledHeight) / 2;
+ int overviewOffsetTopEdge = overviewTop + (overviewBottom - overviewTop - scaledHeight) / 2;
+ return -workspaceOffsetTopEdge + overviewOffsetTopEdge;
}
/**
@@ -1972,10 +2000,10 @@
* to that new state.
*/
public Animator setStateWithAnimation(State toState, boolean animated,
- boolean hasOverlaySearchBar, HashMap<View, Integer> layerViews) {
+ HashMap<View, Integer> layerViews) {
// Create the animation to the new state
Animator workspaceAnim = mStateTransitionAnimation.getAnimationToState(mState,
- toState, animated, hasOverlaySearchBar, layerViews);
+ toState, animated, layerViews);
// Update the current state
mState = toState;
@@ -1993,7 +2021,7 @@
}
public void updateAccessibilityFlags() {
- if (Utilities.isLmpOrAbove()) {
+ if (Utilities.ATLEAST_LOLLIPOP) {
int total = getPageCount();
for (int i = numCustomPages(); i < total; i++) {
updateAccessibilityFlags((CellLayout) getPageAt(i), i);
@@ -2102,19 +2130,17 @@
* @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).
@@ -2300,15 +2326,15 @@
throw new IllegalStateException(msg);
}
+ if (child.getParent() instanceof ShortcutAndWidgetContainer) {
+ mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent();
+ }
+
DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source,
(ItemInfo) dragObject, DragController.DRAG_ACTION_MOVE, dragVisualizeOffset,
dragRect, scale, accessible);
dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());
- if (child.getParent() instanceof ShortcutAndWidgetContainer) {
- mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent();
- }
-
b.recycle();
}
@@ -2384,7 +2410,7 @@
if (mLauncher.isHotseatLayout(dropTargetLayout)) {
mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
} else {
- mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null);
+ mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter);
}
int spanX = 1;
@@ -2590,7 +2616,7 @@
if (mLauncher.isHotseatLayout(dropTargetLayout)) {
mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
} else {
- mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null);
+ mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter);
}
}
@@ -2675,7 +2701,7 @@
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],
@@ -2821,45 +2847,6 @@
}
}
- /** 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
public void onDragExit(DragObject d) {
if (ENFORCE_DRAG_EVENT_ORDER) {
@@ -2998,13 +2985,8 @@
*
* 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();
}
@@ -3069,10 +3051,7 @@
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(sTmpInvMatrix);
- mapPointFromSelfToChild(cl, touchXy, sTmpInvMatrix);
+ mapPointFromSelfToChild(cl, touchXy);
if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() &&
touchXy[1] >= 0 && touchXy[1] <= cl.getHeight()) {
@@ -3117,7 +3096,7 @@
CellLayout layout = null;
ItemInfo item = d.dragInfo;
if (item == null) {
- if (LauncherAppState.isDogfoodBuild()) {
+ if (ProviderConfig.IS_DOGFOOD_BUILD) {
throw new NullPointerException("DragObject has null info");
}
return;
@@ -3173,7 +3152,7 @@
if (mLauncher.isHotseatLayout(mDragTargetLayout)) {
mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
} else {
- mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter, null);
+ mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter);
}
ItemInfo info = d.dragInfo;
@@ -3725,7 +3704,7 @@
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. ");
};
@@ -3745,8 +3724,12 @@
CellLayout parentCell = getParentCellLayoutForView(v);
if (parentCell != null) {
parentCell.removeView(v);
- } else if (LauncherAppState.isDogfoodBuild()) {
- throw new NullPointerException("mDragInfo.cell has null parent");
+ } 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
+ // {@link Launcher#mOnResumeCallbacks} depending on how busy the worker thread is.
+ Log.e(TAG, "mDragInfo.cell has null parent");
}
if (v instanceof DropTarget) {
mDragController.removeDropTarget((DropTarget) v);
@@ -4010,6 +3993,16 @@
});
}
+ public View getHomescreenIconByItemId(final long id) {
+ return getFirstMatch(new ItemOperator() {
+
+ @Override
+ public boolean evaluate(ItemInfo info, View v, View parent) {
+ return info.id == id;
+ }
+ });
+ }
+
public View getViewForTag(final Object tag) {
return getFirstMatch(new ItemOperator() {
@@ -4258,8 +4251,9 @@
updates.contains(info)) {
ShortcutInfo si = (ShortcutInfo) info;
BubbleTextView shortcut = (BubbleTextView) v;
- boolean oldPromiseState = getTextViewIcon(shortcut)
- instanceof PreloadIconDrawable;
+ Drawable oldIcon = getTextViewIcon(shortcut);
+ boolean oldPromiseState = (oldIcon instanceof PreloadIconDrawable)
+ && ((PreloadIconDrawable) oldIcon).hasNotCompleted();
shortcut.applyFromShortcutInfo(si, mIconCache,
si.isPromise() != oldPromiseState);
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 5d16cd5..011f4a2 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -30,6 +30,7 @@
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;
@@ -210,8 +211,9 @@
mOverlayTransitionTime = res.getInteger(R.integer.config_overlayTransitionTime);
mSpringLoadedShrinkFactor =
res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100f;
+ mOverviewModeShrinkFactor =
+ res.getInteger(R.integer.config_workspaceOverviewShrinkPercentage) / 100f;
mWorkspaceScrimAlpha = res.getInteger(R.integer.config_workspaceScrimAlpha) / 100f;
- mOverviewModeShrinkFactor = grid.getOverviewModeScale(Utilities.isRtl(res));
mWorkspaceFadeInAdjacentScreens = grid.shouldFadeAdjacentWorkspaceScreens();
}
@@ -220,14 +222,13 @@
}
public AnimatorSet getAnimationToState(Workspace.State fromState, Workspace.State toState,
- boolean animated, boolean hasOverlaySearchBar, 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 duration = getAnimationDuration(states);
- animateWorkspace(states, animated, duration, layerViews, accessibilityEnabled);
- animateSearchBar(states, animated, duration, hasOverlaySearchBar, layerViews,
+ int workspaceDuration = getAnimationDuration(states);
+ animateWorkspace(states, animated, workspaceDuration, layerViews,
accessibilityEnabled);
animateBackgroundGradient(states, animated, BACKGROUND_FADE_OUT_DURATION);
return mStateAnimator;
@@ -378,7 +379,6 @@
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);
@@ -470,75 +470,9 @@
}
/**
- * Coordinates with the workspace animation to animate the search bar.
- *
- * TODO: This should really be coordinated with the SearchDropTargetBar, otherwise the
- * bar has no idea that it is hidden, and this has no idea what state the bar is
- * actually in.
- */
- private void animateSearchBar(TransitionStates states, boolean animated, int duration,
- boolean hasOverlaySearchBar, final HashMap<View, Integer> layerViews,
- final boolean accessibilityEnabled) {
-
- // The search bar is only visible in the workspace
- final View searchBar = mLauncher.getOrCreateQsbBar();
- if (searchBar != null) {
- final boolean searchBarWillBeShown = states.stateIsNormal;
- final float finalSearchBarAlpha = searchBarWillBeShown ? 1f : 0f;
- if (animated) {
- if (hasOverlaySearchBar) {
- // If there is an overlay search bar, then we will coordinate with it.
- mStateAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- // If we are transitioning to a visible search bar, show it immediately
- // and let the overlay search bar has faded out
- if (searchBarWillBeShown) {
- searchBar.setAlpha(finalSearchBarAlpha);
- AlphaUpdateListener.updateVisibility(searchBar, accessibilityEnabled);
- }
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- // If we are transitioning to a hidden search bar, hide it only after
- // the overlay search bar has faded in
- if (!searchBarWillBeShown) {
- searchBar.setAlpha(finalSearchBarAlpha);
- AlphaUpdateListener.updateVisibility(searchBar, accessibilityEnabled);
- }
- }
- });
- } else {
- // Otherwise, we can just do the normal animation
- LauncherViewPropertyAnimator searchBarAlpha =
- new LauncherViewPropertyAnimator(searchBar).alpha(finalSearchBarAlpha);
- searchBarAlpha.addListener(new AlphaUpdateListener(searchBar,
- accessibilityEnabled));
- searchBar.setLayerType(View.LAYER_TYPE_HARDWARE, null);
- if (layerViews != null) {
- // If layerViews is not null, we add these views, and indicate that
- // the caller can manage layer state.
- layerViews.put(searchBar, LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER);
- } else {
- // Otherwise let the animator handle layer management.
- searchBarAlpha.withLayer();
- }
- searchBarAlpha.setDuration(duration);
- mStateAnimator.play(searchBarAlpha);
- }
- } else {
- // Set the search bar state immediately
- searchBar.setAlpha(finalSearchBarAlpha);
- AlphaUpdateListener.updateVisibility(searchBar, accessibilityEnabled);
- }
- }
- }
-
- /**
* Animates the background scrim. Add to the state animator to prevent jankiness.
*
- * @param finalAlpha the final alpha for the background scrim
+ * @param states the current and final workspace states
* @param animated whether or not to set the background alpha immediately
* @duration duration of the animation
*/
@@ -553,8 +487,7 @@
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 c11aab9..9479685 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.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.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;
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 32c9012..a172d02 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -16,34 +16,26 @@
package com.android.launcher3.allapps;
import android.annotation.SuppressLint;
-import android.annotation.TargetApi;
import android.content.Context;
+import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.InsetDrawable;
-import android.os.Build;
-import android.os.Bundle;
import android.support.v7.widget.RecyclerView;
import android.text.Selection;
import android.text.SpannableStringBuilder;
import android.text.method.TextKeyListener;
import android.util.AttributeSet;
import android.view.KeyEvent;
-import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
-import android.widget.FrameLayout;
import android.widget.LinearLayout;
-
import com.android.launcher3.AppInfo;
import com.android.launcher3.BaseContainerView;
-import com.android.launcher3.BubbleTextView;
import com.android.launcher3.CellLayout;
-import com.android.launcher3.CheckLongPressHelper;
import com.android.launcher3.DeleteDropTarget;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DragSource;
@@ -53,7 +45,6 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherTransitionable;
import com.android.launcher3.R;
-import com.android.launcher3.Stats;
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
import com.android.launcher3.util.ComponentKey;
@@ -155,6 +146,7 @@
@Thunk AllAppsSearchBarController mSearchBarController;
private ViewGroup mSearchBarContainerView;
private View mSearchBarView;
+ private SpannableStringBuilder mSearchQueryBuilder = null;
private int mSectionNamesMargin;
private int mNumAppsPerRow;
@@ -165,7 +157,13 @@
// This coordinate is relative to its parent
private final Point mIconLastTouchPos = new Point();
- private SpannableStringBuilder mSearchQueryBuilder = null;
+ private View.OnClickListener mSearchClickListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Intent searchIntent = (Intent) v.getTag();
+ mLauncher.startActivitySafely(v, searchIntent, null);
+ }
+ };
public AllAppsContainerView(Context context) {
this(context, null);
@@ -182,8 +180,7 @@
mLauncher = (Launcher) context;
mSectionNamesMargin = res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin);
mApps = new AlphabeticalAppsList(context);
- mAdapter = new AllAppsGridAdapter(context, mApps, this, mLauncher, this);
- mAdapter.setEmptySearchText(res.getString(R.string.all_apps_loading_message));
+ mAdapter = new AllAppsGridAdapter(mLauncher, mApps, this, mLauncher, this);
mApps.setAdapter(mAdapter);
mLayoutManager = mAdapter.getLayoutManager();
mItemDecoration = mAdapter.getItemDecoration();
@@ -528,7 +525,6 @@
CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen);
ItemInfo itemInfo = d.dragInfo;
if (layout != null) {
- layout.calculateSpans(itemInfo);
showOutOfSpaceMessage =
!layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY);
}
@@ -559,8 +555,9 @@
@Override
public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
if (toWorkspace) {
- // Reset the search bar after transitioning home
+ // Reset the search bar and base recycler view after transitioning home
mSearchBarController.reset();
+ mAppsRecyclerView.reset();
}
}
@@ -616,13 +613,9 @@
@Override
public void onSearchResult(String query, ArrayList<ComponentKey> apps) {
if (apps != null) {
- if (apps.isEmpty()) {
- String formatStr = getResources().getString(R.string.all_apps_no_search_results);
- mAdapter.setEmptySearchText(String.format(formatStr, query));
- } else {
- mAppsRecyclerView.scrollToTop();
- }
mApps.setOrderedFilter(apps);
+ mAdapter.setLastSearchQuery(query);
+ mAppsRecyclerView.scrollToTop();
}
}
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 057883c..affd32a 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -16,21 +16,28 @@
package com.android.launcher3.allapps;
import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.Rect;
-import android.os.Handler;
+import android.support.v4.view.accessibility.AccessibilityRecordCompat;
+import android.support.v4.view.accessibility.AccessibilityEventCompat;
+import android.net.Uri;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
import android.widget.TextView;
import com.android.launcher3.AppInfo;
import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.util.Thunk;
@@ -42,7 +49,7 @@
/**
* The grid view adapter of all the apps.
*/
-class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHolder> {
+public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHolder> {
public static final String TAG = "AppsGridAdapter";
private static final boolean DEBUG = false;
@@ -55,6 +62,10 @@
public static final int PREDICTION_ICON_VIEW_TYPE = 2;
// The message shown when there are no filtered results
public static final int EMPTY_SEARCH_VIEW_TYPE = 3;
+ // A divider that separates the apps list and the search market button
+ public static final int SEARCH_MARKET_DIVIDER_VIEW_TYPE = 4;
+ // The message to continue to a market search when there are no filtered results
+ public static final int SEARCH_MARKET_VIEW_TYPE = 5;
/**
* ViewHolder for each icon.
@@ -69,6 +80,38 @@
}
/**
+ * A subclass of GridLayoutManager that overrides accessibility values during app search.
+ */
+ public class AppsGridLayoutManager extends GridLayoutManager {
+
+ public AppsGridLayoutManager(Context context) {
+ super(context, 1, GridLayoutManager.VERTICAL, false);
+ }
+
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
+
+ // Ensure that we only report the number apps for accessibility not including other
+ // adapter views
+ final AccessibilityRecordCompat record = AccessibilityEventCompat
+ .asRecord(event);
+ record.setItemCount(mApps.getNumFilteredApps());
+ }
+
+ @Override
+ public int getRowCountForAccessibility(RecyclerView.Recycler recycler,
+ RecyclerView.State state) {
+ if (mApps.hasNoFilteredResults()) {
+ // Disregard the no-search-results text as a list item for accessibility
+ return 0;
+ } else {
+ return super.getRowCountForAccessibility(recycler, state);
+ }
+ }
+ }
+
+ /**
* Helper class to size the grid items.
*/
public class GridSpanSizer extends GridLayoutManager.SpanSizeLookup {
@@ -80,11 +123,6 @@
@Override
public int getSpanSize(int position) {
- if (mApps.hasNoFilteredResults()) {
- // Empty view spans full width
- return mAppsPerRow;
- }
-
switch (mApps.getAdapterItems().get(position).viewType) {
case AllAppsGridAdapter.ICON_VIEW_TYPE:
case AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE:
@@ -279,6 +317,7 @@
}
}
+ @Thunk Launcher mLauncher;
private LayoutInflater mLayoutInflater;
@Thunk AlphabeticalAppsList mApps;
private GridLayoutManager mGridLayoutMgr;
@@ -291,7 +330,19 @@
@Thunk int mPredictionBarDividerOffset;
@Thunk int mAppsPerRow;
@Thunk boolean mIsRtl;
- private String mEmptySearchText;
+
+ // The text to show when there are no search results and no market search handler.
+ private String mEmptySearchMessage;
+ // The name of the market app which handles searches, to be used in the format str
+ // below when updating the search-market view. Only needs to be loaded once.
+ private String mMarketAppName;
+ // The text to show when there is a market app which can handle a specific query, updated
+ // 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.
+ @Thunk Intent mMarketSearchIntent;
+ // The last query that the user entered into the search field
+ @Thunk String mLastSearchQuery;
// Section drawing
@Thunk int mSectionNamesMargin;
@@ -299,16 +350,18 @@
@Thunk Paint mSectionTextPaint;
@Thunk Paint mPredictedAppsDividerPaint;
- public AllAppsGridAdapter(Context context, AlphabeticalAppsList apps,
+ public AllAppsGridAdapter(Launcher launcher, AlphabeticalAppsList apps,
View.OnTouchListener touchListener, View.OnClickListener iconClickListener,
View.OnLongClickListener iconLongClickListener) {
- Resources res = context.getResources();
+ Resources res = launcher.getResources();
+ mLauncher = launcher;
mApps = apps;
+ mEmptySearchMessage = res.getString(R.string.all_apps_loading_message);
mGridSizer = new GridSpanSizer();
- mGridLayoutMgr = new GridLayoutManager(context, 1, GridLayoutManager.VERTICAL, false);
+ mGridLayoutMgr = new AppsGridLayoutManager(launcher);
mGridLayoutMgr.setSpanSizeLookup(mGridSizer);
mItemDecoration = new GridItemDecoration();
- mLayoutInflater = LayoutInflater.from(context);
+ mLayoutInflater = LayoutInflater.from(launcher);
mTouchListener = touchListener;
mIconClickListener = iconClickListener;
mIconLongClickListener = iconLongClickListener;
@@ -328,6 +381,14 @@
mPredictionBarDividerOffset =
(-res.getDimensionPixelSize(R.dimen.all_apps_prediction_icon_bottom_padding) +
res.getDimensionPixelSize(R.dimen.all_apps_icon_top_bottom_padding)) / 2;
+
+ // Resolve the market app handling additional searches
+ PackageManager pm = launcher.getPackageManager();
+ ResolveInfo marketInfo = pm.resolveActivity(createMarketSearchIntent(""),
+ PackageManager.MATCH_DEFAULT_ONLY);
+ if (marketInfo != null) {
+ mMarketAppName = marketInfo.loadLabel(pm).toString();
+ }
}
/**
@@ -346,10 +407,19 @@
}
/**
- * Sets the text to show when there are no apps.
+ * Sets the last search query that was made, used to show when there are no results and to also
+ * seed the intent for searching the market.
*/
- public void setEmptySearchText(String query) {
- mEmptySearchText = query;
+ 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);
+ if (mMarketAppName != null) {
+ mMarketSearchMessage = String.format(res.getString(R.string.all_apps_search_market_message),
+ mMarketAppName);
+ mMarketSearchIntent = createMarketSearchIntent(query);
+ }
}
/**
@@ -378,9 +448,6 @@
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
- case EMPTY_SEARCH_VIEW_TYPE:
- return new ViewHolder(mLayoutInflater.inflate(R.layout.all_apps_empty_search, parent,
- false));
case SECTION_BREAK_VIEW_TYPE:
return new ViewHolder(new View(parent.getContext()));
case ICON_VIEW_TYPE: {
@@ -405,6 +472,22 @@
icon.setFocusable(true);
return new ViewHolder(icon);
}
+ case EMPTY_SEARCH_VIEW_TYPE:
+ return new ViewHolder(mLayoutInflater.inflate(R.layout.all_apps_empty_search,
+ parent, false));
+ case SEARCH_MARKET_DIVIDER_VIEW_TYPE:
+ return new ViewHolder(mLayoutInflater.inflate(R.layout.all_apps_search_market_divider,
+ parent, false));
+ case SEARCH_MARKET_VIEW_TYPE:
+ View searchMarketView = mLayoutInflater.inflate(R.layout.all_apps_search_market,
+ parent, false);
+ searchMarketView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mLauncher.startSearchFromAllApps(v, mMarketSearchIntent, mLastSearchQuery);
+ }
+ });
+ return new ViewHolder(searchMarketView);
default:
throw new RuntimeException("Unexpected view type");
}
@@ -426,28 +509,44 @@
break;
}
case EMPTY_SEARCH_VIEW_TYPE:
- TextView emptyViewText = (TextView) holder.mContent.findViewById(R.id.empty_text);
- emptyViewText.setText(mEmptySearchText);
+ TextView emptyViewText = (TextView) holder.mContent;
+ emptyViewText.setText(mEmptySearchMessage);
+ break;
+ case SEARCH_MARKET_VIEW_TYPE:
+ View searchView = holder.mContent;
+ if (mMarketSearchIntent != null) {
+ searchView.setVisibility(View.VISIBLE);
+ searchView.setContentDescription(mMarketSearchMessage);
+ ((TextView) searchView.findViewById(R.id.search_market_text))
+ .setText(mMarketSearchMessage);
+ } else {
+ searchView.setVisibility(View.GONE);
+ }
break;
}
}
@Override
public int getItemCount() {
- if (mApps.hasNoFilteredResults()) {
- // For the empty view
- return 1;
- }
return mApps.getAdapterItems().size();
}
@Override
public int getItemViewType(int position) {
- if (mApps.hasNoFilteredResults()) {
- return EMPTY_SEARCH_VIEW_TYPE;
- }
-
AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(position);
return item.viewType;
}
+
+ /**
+ * Creates a new market search intent.
+ */
+ private Intent createMarketSearchIntent(String query) {
+ Uri marketSearchUri = Uri.parse("market://search")
+ .buildUpon()
+ .appendQueryParameter("q", query)
+ .build();
+ Intent marketSearchIntent = new Intent(Intent.ACTION_VIEW);
+ marketSearchIntent.setData(marketSearchUri);
+ return marketSearchIntent;
+ }
}
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 730c8d1..5ec8bb2 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -72,6 +72,7 @@
public AllAppsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr);
+ mScrollbar.setDetachThumbOnFastScroll();
}
/**
@@ -90,6 +91,8 @@
RecyclerView.RecycledViewPool pool = getRecycledViewPool();
int approxRows = (int) Math.ceil(grid.availableHeightPx / grid.allAppsIconSizePx);
pool.setMaxRecycledViews(AllAppsGridAdapter.EMPTY_SEARCH_VIEW_TYPE, 1);
+ pool.setMaxRecycledViews(AllAppsGridAdapter.SEARCH_MARKET_DIVIDER_VIEW_TYPE, 1);
+ pool.setMaxRecycledViews(AllAppsGridAdapter.SEARCH_MARKET_VIEW_TYPE, 1);
pool.setMaxRecycledViews(AllAppsGridAdapter.ICON_VIEW_TYPE, approxRows * mNumAppsPerRow);
pool.setMaxRecycledViews(AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE, mNumAppsPerRow);
pool.setMaxRecycledViews(AllAppsGridAdapter.SECTION_BREAK_VIEW_TYPE, approxRows);
@@ -166,8 +169,8 @@
}
// Map the touch position back to the scroll of the recycler view
- getCurScrollState(mScrollPosState, mApps.getAdapterItems());
- int availableScrollHeight = getAvailableScrollHeight(rowCount, mScrollPosState.rowHeight, 0);
+ getCurScrollState(mScrollPosState);
+ int availableScrollHeight = getAvailableScrollHeight(rowCount, mScrollPosState.rowHeight);
LinearLayoutManager layoutManager = (LinearLayoutManager) getLayoutManager();
if (mFastScrollMode == FAST_SCROLL_MODE_FREE_SCROLL) {
layoutManager.scrollToPositionWithOffset(0, (int) -(availableScrollHeight * touchFraction));
@@ -214,24 +217,73 @@
* Updates the bounds for the scrollbar.
*/
@Override
- public void onUpdateScrollbar() {
+ public void onUpdateScrollbar(int dy) {
List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
// Skip early if there are no items or we haven't been measured
if (items.isEmpty() || mNumAppsPerRow == 0) {
- mScrollbar.setScrollbarThumbOffset(-1, -1);
+ mScrollbar.setThumbOffset(-1, -1);
return;
}
// Find the index and height of the first visible row (all rows have the same height)
int rowCount = mApps.getNumAppRows();
- getCurScrollState(mScrollPosState, items);
+ getCurScrollState(mScrollPosState);
if (mScrollPosState.rowIndex < 0) {
- mScrollbar.setScrollbarThumbOffset(-1, -1);
+ mScrollbar.setThumbOffset(-1, -1);
return;
}
- synchronizeScrollBarThumbOffsetToViewScroll(mScrollPosState, rowCount, 0);
+ // Only show the scrollbar if there is height to be scrolled
+ int availableScrollBarHeight = getAvailableScrollBarHeight();
+ int availableScrollHeight = getAvailableScrollHeight(mApps.getNumAppRows(), mScrollPosState.rowHeight);
+ if (availableScrollHeight <= 0) {
+ mScrollbar.setThumbOffset(-1, -1);
+ return;
+ }
+
+ // Calculate the current scroll position, the scrollY of the recycler view accounts for the
+ // view padding, while the scrollBarY is drawn right up to the background padding (ignoring
+ // padding)
+ int scrollY = getPaddingTop() +
+ (mScrollPosState.rowIndex * mScrollPosState.rowHeight) - mScrollPosState.rowTopOffset;
+ int scrollBarY = mBackgroundPadding.top +
+ (int) (((float) scrollY / availableScrollHeight) * availableScrollBarHeight);
+
+ if (mScrollbar.isThumbDetached()) {
+ int scrollBarX;
+ if (Utilities.isRtl(getResources())) {
+ scrollBarX = mBackgroundPadding.left;
+ } else {
+ scrollBarX = getWidth() - mBackgroundPadding.right - mScrollbar.getThumbWidth();
+ }
+
+ if (mScrollbar.isDraggingThumb()) {
+ // If the thumb is detached, then just update the thumb to the current
+ // touch position
+ mScrollbar.setThumbOffset(scrollBarX, (int) mScrollbar.getLastTouchY());
+ } else {
+ int thumbScrollY = mScrollbar.getThumbOffset().y;
+ int diffScrollY = scrollBarY - thumbScrollY;
+ if (diffScrollY * dy > 0f) {
+ // User is scrolling in the same direction the thumb needs to catch up to the
+ // current scroll position.
+ thumbScrollY += dy < 0 ? Math.max(dy, diffScrollY) : Math.min(dy, diffScrollY);
+ thumbScrollY = Math.max(0, Math.min(availableScrollBarHeight, thumbScrollY));
+ mScrollbar.setThumbOffset(scrollBarX, thumbScrollY);
+ if (scrollBarY == thumbScrollY) {
+ mScrollbar.reattachThumbToScroll();
+ }
+ } else {
+ // User is scrolling in an opposite direction to the direction that the thumb
+ // needs to catch up to the scroll position. Do nothing except for updating
+ // the scroll bar x to match the thumb width.
+ mScrollbar.setThumbOffset(scrollBarX, thumbScrollY);
+ }
+ }
+ } else {
+ synchronizeScrollBarThumbOffsetToViewScroll(mScrollPosState, rowCount);
+ }
}
/**
@@ -283,13 +335,13 @@
/**
* Returns the current scroll state of the apps rows.
*/
- private void getCurScrollState(ScrollPositionState stateOut,
- List<AlphabeticalAppsList.AdapterItem> items) {
+ protected void getCurScrollState(ScrollPositionState stateOut) {
stateOut.rowIndex = -1;
stateOut.rowTopOffset = -1;
stateOut.rowHeight = -1;
// Return early if there are no items or we haven't been measured
+ List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
if (items.isEmpty() || mNumAppsPerRow == 0) {
return;
}
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java
index 14e2a18..09a7d59 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java
@@ -17,17 +17,15 @@
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 2b363c0..d853d5b 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 47241ce..cb989e5 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;
@@ -43,6 +43,11 @@
private static final boolean DEBUG = false;
private static final boolean DEBUG_PREDICTIONS = false;
+ private static final int FAST_SCROLL_FRACTION_DISTRIBUTE_BY_ROWS_FRACTION = 0;
+ private static final int FAST_SCROLL_FRACTION_DISTRIBUTE_BY_NUM_SECTIONS = 1;
+
+ private final int mFastScrollDistributionMode = FAST_SCROLL_FRACTION_DISTRIBUTE_BY_NUM_SECTIONS;
+
/**
* Info about a section in the alphabetic list
*/
@@ -81,8 +86,6 @@
public int position;
// The type of this item
public int viewType;
- // The row that this item shows up on
- public int rowIndex;
/** Section & App properties */
// The section for this item
@@ -94,6 +97,8 @@
public String sectionName = null;
// The index of this app in the section
public int sectionAppIndex = -1;
+ // The row that this item shows up on
+ public int rowIndex;
// The index of this app in the row
public int rowAppIndex;
// The associated AppInfo for the app
@@ -111,14 +116,14 @@
}
public static AdapterItem asPredictedApp(int pos, SectionInfo section, String sectionName,
- int sectionAppIndex, AppInfo appInfo, int appIndex) {
+ int sectionAppIndex, AppInfo appInfo, int appIndex) {
AdapterItem item = asApp(pos, section, sectionName, sectionAppIndex, appInfo, appIndex);
item.viewType = AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE;
return item;
}
public static AdapterItem asApp(int pos, SectionInfo section, String sectionName,
- int sectionAppIndex, AppInfo appInfo, int appIndex) {
+ int sectionAppIndex, AppInfo appInfo, int appIndex) {
AdapterItem item = new AdapterItem();
item.viewType = AllAppsGridAdapter.ICON_VIEW_TYPE;
item.position = pos;
@@ -129,6 +134,27 @@
item.appIndex = appIndex;
return item;
}
+
+ public static AdapterItem asEmptySearch(int pos) {
+ AdapterItem item = new AdapterItem();
+ item.viewType = AllAppsGridAdapter.EMPTY_SEARCH_VIEW_TYPE;
+ item.position = pos;
+ return item;
+ }
+
+ public static AdapterItem asDivider(int pos) {
+ AdapterItem item = new AdapterItem();
+ item.viewType = AllAppsGridAdapter.SEARCH_MARKET_DIVIDER_VIEW_TYPE;
+ item.position = pos;
+ return item;
+ }
+
+ public static AdapterItem asMarketSearch(int pos) {
+ AdapterItem item = new AdapterItem();
+ item.viewType = AllAppsGridAdapter.SEARCH_MARKET_VIEW_TYPE;
+ item.position = pos;
+ return item;
+ }
}
/**
@@ -160,7 +186,7 @@
// 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;
@@ -189,7 +215,7 @@
/**
* Sets the adapter to notify when this dataset changes.
*/
- public void setAdapter(RecyclerView.Adapter adapter) {
+ public void setAdapter(AllAppsGridAdapter adapter) {
mAdapter = adapter;
}
@@ -222,13 +248,6 @@
}
/**
- * Returns the number of applications in this list.
- */
- public int getSize() {
- return mFilteredApps.size();
- }
-
- /**
* Returns the number of rows of applications (not including predictions)
*/
public int getNumAppRows() {
@@ -236,6 +255,13 @@
}
/**
+ * Returns the number of applications in this list.
+ */
+ public int getNumFilteredApps() {
+ return mFilteredApps.size();
+ }
+
+ /**
* Returns whether there are is a filter set.
*/
public boolean hasFilter() {
@@ -393,7 +419,7 @@
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));
}
}
@@ -457,6 +483,16 @@
mFilteredApps.add(info);
}
+ // Append the search market item if we are currently searching
+ if (hasFilter()) {
+ if (hasNoFilteredResults()) {
+ mAdapterItems.add(AdapterItem.asEmptySearch(position++));
+ } else {
+ mAdapterItems.add(AdapterItem.asDivider(position++));
+ }
+ mAdapterItems.add(AdapterItem.asMarketSearch(position++));
+ }
+
// Merge multiple sections together as requested by the merge strategy for this device
mergeSections();
@@ -484,18 +520,36 @@
}
mNumAppRowsInAdapter = rowIndex + 1;
- // Pre-calculate all the fast scroller fractions based on the number of rows
- float rowFraction = 1f / mNumAppRowsInAdapter;
- for (FastScrollSectionInfo info : mFastScrollerSections) {
- AdapterItem item = info.fastScrollToItem;
- if (item.viewType != AllAppsGridAdapter.ICON_VIEW_TYPE &&
- item.viewType != AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE) {
- info.touchFraction = 0f;
- continue;
- }
+ // Pre-calculate all the fast scroller fractions
+ switch (mFastScrollDistributionMode) {
+ case FAST_SCROLL_FRACTION_DISTRIBUTE_BY_ROWS_FRACTION:
+ float rowFraction = 1f / mNumAppRowsInAdapter;
+ for (FastScrollSectionInfo info : mFastScrollerSections) {
+ AdapterItem item = info.fastScrollToItem;
+ if (item.viewType != AllAppsGridAdapter.ICON_VIEW_TYPE &&
+ item.viewType != AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE) {
+ info.touchFraction = 0f;
+ continue;
+ }
- float subRowFraction = item.rowAppIndex * (rowFraction / mNumAppsPerRow);
- info.touchFraction = item.rowIndex * rowFraction + subRowFraction;
+ float subRowFraction = item.rowAppIndex * (rowFraction / mNumAppsPerRow);
+ info.touchFraction = item.rowIndex * rowFraction + subRowFraction;
+ }
+ break;
+ case FAST_SCROLL_FRACTION_DISTRIBUTE_BY_NUM_SECTIONS:
+ float perSectionTouchFraction = 1f / mFastScrollerSections.size();
+ float cumulativeTouchFraction = 0f;
+ for (FastScrollSectionInfo info : mFastScrollerSections) {
+ AdapterItem item = info.fastScrollToItem;
+ if (item.viewType != AllAppsGridAdapter.ICON_VIEW_TYPE &&
+ item.viewType != AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE) {
+ info.touchFraction = 0f;
+ continue;
+ }
+ info.touchFraction = cumulativeTouchFraction;
+ cumulativeTouchFraction += perSectionTouchFraction;
+ }
+ break;
}
}
diff --git a/src/com/android/launcher3/allapps/DefaultAppSearchController.java b/src/com/android/launcher3/allapps/DefaultAppSearchController.java
index 83b9205..3169f84 100644
--- a/src/com/android/launcher3/allapps/DefaultAppSearchController.java
+++ b/src/com/android/launcher3/allapps/DefaultAppSearchController.java
@@ -25,6 +25,7 @@
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.TextView;
+import com.android.launcher3.ExtendedEditText;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.util.Thunk;
@@ -54,7 +55,8 @@
@Thunk View mSearchBarContainerView;
private View mSearchButtonView;
private View mDismissSearchButtonView;
- @Thunk AllAppsSearchEditView mSearchBarEditView;
+ @Thunk
+ ExtendedEditText mSearchBarEditView;
@Thunk AllAppsRecyclerView mAppsRecyclerView;
@Thunk Runnable mFocusRecyclerViewRunnable = new Runnable() {
@Override
@@ -82,21 +84,23 @@
mSearchBarContainerView = mSearchView.findViewById(R.id.search_container);
mDismissSearchButtonView = mSearchBarContainerView.findViewById(R.id.dismiss_search_button);
mDismissSearchButtonView.setOnClickListener(this);
- mSearchBarEditView = (AllAppsSearchEditView)
+ mSearchBarEditView = (ExtendedEditText)
mSearchBarContainerView.findViewById(R.id.search_box_input);
mSearchBarEditView.addTextChangedListener(this);
mSearchBarEditView.setOnEditorActionListener(this);
mSearchBarEditView.setOnBackKeyListener(
- new AllAppsSearchEditView.OnBackKeyListener() {
+ new ExtendedEditText.OnBackKeyListener() {
@Override
- public void onBackKey() {
+ public boolean onBackKey() {
// Only hide the search field if there is no query, or if there
// are no filtered results
String query = Utilities.trim(
mSearchBarEditView.getEditableText().toString());
if (query.isEmpty() || mApps.hasNoFilteredResults()) {
hideSearchField(true, mFocusRecyclerViewRunnable);
+ return true;
}
+ return false;
}
});
return mSearchView;
@@ -166,22 +170,24 @@
return false;
}
// Skip if it's not the right action
- if (actionId != EditorInfo.IME_ACTION_DONE) {
+ if (actionId != EditorInfo.IME_ACTION_SEARCH) {
return false;
}
- // Skip if there isn't exactly one item
- if (mApps.getSize() != 1) {
+ // Skip if there are more than one icon
+ if (mApps.getNumFilteredApps() > 1) {
return false;
}
- // If there is exactly one icon, then quick-launch it
+ // Otherwise, find the first icon, or fallback to the search-market-view and launch it
List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
for (int i = 0; i < items.size(); i++) {
AlphabeticalAppsList.AdapterItem item = items.get(i);
- if (item.viewType == AllAppsGridAdapter.ICON_VIEW_TYPE) {
- mAppsRecyclerView.getChildAt(i).performClick();
- mInputMethodManager.hideSoftInputFromWindow(
- mContainerView.getWindowToken(), 0);
- return true;
+ switch (item.viewType) {
+ case AllAppsGridAdapter.ICON_VIEW_TYPE:
+ case AllAppsGridAdapter.SEARCH_MARKET_VIEW_TYPE:
+ mAppsRecyclerView.getChildAt(i).performClick();
+ mInputMethodManager.hideSoftInputFromWindow(
+ mContainerView.getWindowToken(), 0);
+ return true;
}
}
return false;
diff --git a/src/com/android/launcher3/compat/AlphabeticIndexCompat.java b/src/com/android/launcher3/compat/AlphabeticIndexCompat.java
index ec1fb66..463278a 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 @@
private boolean mHasValidAlphabeticIndex;
private String mDefaultMiscLabel;
+ @SuppressWarnings({"unchecked", "rawtypes"})
public AlphabeticIndexCompat(Context context) {
super();
try {
diff --git a/src/com/android/launcher3/compat/AppWidgetManagerCompat.java b/src/com/android/launcher3/compat/AppWidgetManagerCompat.java
index 7aa36d4..434f13d 100644
--- a/src/com/android/launcher3/compat/AppWidgetManagerCompat.java
+++ b/src/com/android/launcher3/compat/AppWidgetManagerCompat.java
@@ -40,7 +40,7 @@
public static AppWidgetManagerCompat getInstance(Context context) {
synchronized (sInstanceLock) {
if (sInstance == null) {
- if (Utilities.isLmpOrAbove()) {
+ if (Utilities.ATLEAST_LOLLIPOP) {
sInstance = new AppWidgetManagerCompatVL(context.getApplicationContext());
} else {
sInstance = new AppWidgetManagerCompatV16(context.getApplicationContext());
diff --git a/src/com/android/launcher3/compat/AppWidgetManagerCompatV16.java b/src/com/android/launcher3/compat/AppWidgetManagerCompatV16.java
index f7f4b7e..463cf90 100644
--- a/src/com/android/launcher3/compat/AppWidgetManagerCompatV16.java
+++ b/src/com/android/launcher3/compat/AppWidgetManagerCompatV16.java
@@ -54,10 +54,10 @@
@Override
public boolean bindAppWidgetIdIfAllowed(int appWidgetId, AppWidgetProviderInfo info,
Bundle options) {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
- return mAppWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, info.provider);
- } else {
+ if (Utilities.ATLEAST_JB_MR1) {
return mAppWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, info.provider, options);
+ } else {
+ return mAppWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, info.provider);
}
}
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompat.java b/src/com/android/launcher3/compat/LauncherAppsCompat.java
index 5858bc8..95e3ba9 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompat.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompat.java
@@ -53,7 +53,7 @@
public static LauncherAppsCompat getInstance(Context context) {
synchronized (sInstanceLock) {
if (sInstance == null) {
- if (Utilities.isLmpOrAbove()) {
+ if (Utilities.ATLEAST_LOLLIPOP) {
sInstance = new LauncherAppsCompatVL(context.getApplicationContext());
} else {
sInstance = new LauncherAppsCompatV16(context.getApplicationContext());
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatV16.java b/src/com/android/launcher3/compat/LauncherAppsCompatV16.java
index ac3d252..339c457 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompatV16.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompatV16.java
@@ -31,6 +31,7 @@
import android.os.Bundle;
import android.provider.Settings;
+import com.android.launcher3.Utilities;
import com.android.launcher3.util.Thunk;
import java.util.ArrayList;
@@ -188,7 +189,7 @@
// when moving a package or mounting/un-mounting external storage. Assume that
// it is a replacing operation.
final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING,
- Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT);
+ !Utilities.ATLEAST_KITKAT);
String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
for (OnAppsChangedCallbackCompat callback : getCallbacks()) {
callback.onPackagesAvailable(packages, user, replacing);
diff --git a/src/com/android/launcher3/compat/PackageInstallerCompat.java b/src/com/android/launcher3/compat/PackageInstallerCompat.java
index c499083..ec5014d 100644
--- a/src/com/android/launcher3/compat/PackageInstallerCompat.java
+++ b/src/com/android/launcher3/compat/PackageInstallerCompat.java
@@ -34,7 +34,7 @@
public static PackageInstallerCompat getInstance(Context context) {
synchronized (sInstanceLock) {
if (sInstance == null) {
- if (Utilities.isLmpOrAbove()) {
+ if (Utilities.ATLEAST_LOLLIPOP) {
sInstance = new PackageInstallerCompatVL(context);
} else {
sInstance = new PackageInstallerCompatV16();
diff --git a/src/com/android/launcher3/compat/UserHandleCompat.java b/src/com/android/launcher3/compat/UserHandleCompat.java
index ab4b721..567022b 100644
--- a/src/com/android/launcher3/compat/UserHandleCompat.java
+++ b/src/com/android/launcher3/compat/UserHandleCompat.java
@@ -34,7 +34,7 @@
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public static UserHandleCompat myUserHandle() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ if (Utilities.ATLEAST_JB_MR1) {
return new UserHandleCompat(android.os.Process.myUserHandle());
} else {
return new UserHandleCompat();
@@ -55,7 +55,7 @@
@Override
public String toString() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ if (Utilities.ATLEAST_JB_MR1) {
return mUser.toString();
} else {
return "";
@@ -67,7 +67,7 @@
if (!(other instanceof UserHandleCompat)) {
return false;
}
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ if (Utilities.ATLEAST_JB_MR1) {
return mUser.equals(((UserHandleCompat) other).mUser);
} else {
return true;
@@ -76,7 +76,7 @@
@Override
public int hashCode() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ if (Utilities.ATLEAST_JB_MR1) {
return mUser.hashCode();
} else {
return 0;
@@ -89,7 +89,7 @@
* profiles so this is a no-op.
*/
public void addToIntent(Intent intent, String name) {
- if (Utilities.isLmpOrAbove() && mUser != null) {
+ if (Utilities.ATLEAST_LOLLIPOP && mUser != null) {
intent.putExtra(name, mUser);
}
}
diff --git a/src/com/android/launcher3/compat/UserManagerCompat.java b/src/com/android/launcher3/compat/UserManagerCompat.java
index a79d946..f708004 100644
--- a/src/com/android/launcher3/compat/UserManagerCompat.java
+++ b/src/com/android/launcher3/compat/UserManagerCompat.java
@@ -28,16 +28,29 @@
protected UserManagerCompat() {
}
+ private static final Object sInstanceLock = new Object();
+ private static UserManagerCompat sInstance;
+
public static UserManagerCompat getInstance(Context context) {
- if (Utilities.isLmpOrAbove()) {
- return new UserManagerCompatVL(context);
- } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
- return new UserManagerCompatV17(context);
- } else {
- return new UserManagerCompatV16();
+ synchronized (sInstanceLock) {
+ if (sInstance == null) {
+ if (Utilities.ATLEAST_LOLLIPOP) {
+ sInstance = new UserManagerCompatVL(context.getApplicationContext());
+ } else if (Utilities.ATLEAST_JB_MR1) {
+ sInstance = new UserManagerCompatV17(context.getApplicationContext());
+ } else {
+ sInstance = new UserManagerCompatV16();
+ }
+ }
+ return sInstance;
}
}
+ /**
+ * Creates a cache for users.
+ */
+ public abstract void enableAndResetCache();
+
public abstract List<UserHandleCompat> getUserProfiles();
public abstract long getSerialNumberForUser(UserHandleCompat user);
public abstract UserHandleCompat getUserForSerialNumber(long serialNumber);
diff --git a/src/com/android/launcher3/compat/UserManagerCompatV16.java b/src/com/android/launcher3/compat/UserManagerCompatV16.java
index ffe698c..85aee57 100644
--- a/src/com/android/launcher3/compat/UserManagerCompatV16.java
+++ b/src/com/android/launcher3/compat/UserManagerCompatV16.java
@@ -53,4 +53,8 @@
public long getUserCreationTime(UserHandleCompat user) {
return 0;
}
+
+ @Override
+ public void enableAndResetCache() {
+ }
}
diff --git a/src/com/android/launcher3/compat/UserManagerCompatV17.java b/src/com/android/launcher3/compat/UserManagerCompatV17.java
index c42c00c..75203b7 100644
--- a/src/com/android/launcher3/compat/UserManagerCompatV17.java
+++ b/src/com/android/launcher3/compat/UserManagerCompatV17.java
@@ -21,8 +21,18 @@
import android.os.Build;
import android.os.UserManager;
+import com.android.launcher3.util.LongArrayMap;
+
+import java.util.HashMap;
+
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public class UserManagerCompatV17 extends UserManagerCompatV16 {
+
+ protected LongArrayMap<UserHandleCompat> mUsers;
+ // Create a separate reverse map as LongArrayMap.indexOfValue checks if objects are same
+ // and not {@link Object#equals}
+ protected HashMap<UserHandleCompat, Long> mUserToSerialMap;
+
protected UserManager mUserManager;
UserManagerCompatV17(Context context) {
@@ -30,11 +40,34 @@
}
public long getSerialNumberForUser(UserHandleCompat user) {
+ synchronized (this) {
+ if (mUserToSerialMap != null) {
+ Long serial = mUserToSerialMap.get(user);
+ return serial == null ? 0 : serial;
+ }
+ }
return mUserManager.getSerialNumberForUser(user.getUser());
}
public UserHandleCompat getUserForSerialNumber(long serialNumber) {
+ synchronized (this) {
+ if (mUsers != null) {
+ return mUsers.get(serialNumber);
+ }
+ }
return UserHandleCompat.fromUser(mUserManager.getUserForSerialNumber(serialNumber));
}
+
+ @Override
+ public void enableAndResetCache() {
+ synchronized (this) {
+ mUsers = new LongArrayMap<>();
+ mUserToSerialMap = new HashMap<>();
+ UserHandleCompat myUser = UserHandleCompat.myUserHandle();
+ long serial = mUserManager.getSerialNumberForUser(myUser.getUser());
+ mUsers.put(serial, myUser);
+ mUserToSerialMap.put(myUser, serial);
+ }
+ }
}
diff --git a/src/com/android/launcher3/compat/UserManagerCompatVL.java b/src/com/android/launcher3/compat/UserManagerCompatVL.java
index dd7a726..4d404db 100644
--- a/src/com/android/launcher3/compat/UserManagerCompatVL.java
+++ b/src/com/android/launcher3/compat/UserManagerCompatVL.java
@@ -24,9 +24,13 @@
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.UserHandle;
+
import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.util.LongArrayMap;
+
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@@ -43,7 +47,32 @@
}
@Override
+ public void enableAndResetCache() {
+ synchronized (this) {
+ mUsers = new LongArrayMap<>();
+ mUserToSerialMap = new HashMap<>();
+ List<UserHandle> users = mUserManager.getUserProfiles();
+ if (users != null) {
+ for (UserHandle user : users) {
+ long serial = mUserManager.getSerialNumberForUser(user);
+ UserHandleCompat userCompat = UserHandleCompat.fromUser(user);
+ mUsers.put(serial, userCompat);
+ mUserToSerialMap.put(userCompat, serial);
+ }
+ }
+ }
+ }
+
+ @Override
public List<UserHandleCompat> getUserProfiles() {
+ synchronized (this) {
+ if (mUsers != null) {
+ List<UserHandleCompat> users = new ArrayList<>();
+ users.addAll(mUserToSerialMap.keySet());
+ return users;
+ }
+ }
+
List<UserHandle> users = mUserManager.getUserProfiles();
if (users == null) {
return Collections.emptyList();
diff --git a/src/com/android/launcher3/config/ProviderConfig.java b/src/com/android/launcher3/config/ProviderConfig.java
index e8930d0..825b434 100644
--- a/src/com/android/launcher3/config/ProviderConfig.java
+++ b/src/com/android/launcher3/config/ProviderConfig.java
@@ -19,4 +19,6 @@
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
similarity index 83%
rename from src/com/android/launcher3/DragController.java
rename to src/com/android/launcher3/dragndrop/DragController.java
index 410271d..8777dc6 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.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,25 @@
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.Utilities;
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 +70,9 @@
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;
@@ -73,10 +84,13 @@
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. */
@@ -88,7 +102,7 @@
/** the area at the edge of the screen that makes the workspace go left
* or right while you're dragging.
*/
- private int mScrollZone;
+ private final int mScrollZone;
private DropTarget.DragObject mDragObject;
@@ -120,7 +134,7 @@
private int mTmpPoint[] = new int[2];
private Rect mDragLayerRect = new Rect();
- protected int mFlingToDeleteThresholdVelocity;
+ protected final int mFlingToDeleteThresholdVelocity;
private VelocityTracker mVelocityTracker;
/**
@@ -155,16 +169,11 @@
mScrollZone = r.getDimensionPixelSize(R.dimen.scroll_zone);
mVelocityTracker = VelocityTracker.obtain();
- float density = r.getDisplayMetrics().density;
mFlingToDeleteThresholdVelocity =
- (int) (r.getInteger(R.integer.config_flingToDeleteMinVelocity) * density);
+ r.getDimensionPixelSize(R.dimen.drag_flingToDeleteMinVelocity);
mIsRtl = Utilities.isRtl(r);
}
- public boolean dragging() {
- return mDragging;
- }
-
/**
* Starts a drag.
*
@@ -234,8 +243,8 @@
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 +265,10 @@
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 +331,18 @@
* </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 +353,7 @@
}
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 +377,8 @@
}
private void endDrag() {
- if (mDragging) {
- mDragging = false;
+ if (isDragging()) {
+ mDragDriver = null;
mIsAccessibleDrag = false;
clearScrollRunnable();
boolean isDeferred = false;
@@ -415,18 +429,66 @@
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 +496,8 @@
@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 +513,39 @@
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;
}
@@ -579,7 +639,7 @@
* Call this from a drag source view.
*/
public boolean onTouchEvent(MotionEvent ev) {
- if (!mDragging || mIsAccessibleDrag) {
+ if (mDragDriver == null || mIsAccessibleDrag) {
return false;
}
@@ -592,47 +652,25 @@
final int dragLayerY = dragLayerPos[1];
switch (action) {
- case MotionEvent.ACTION_DOWN:
- // Remember where the motion event started
- mMotionDownX = dragLayerX;
- mMotionDownY = dragLayerY;
+ 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);
+ if ((dragLayerX < mScrollZone) || (dragLayerX > mScrollView.getWidth() - mScrollZone)) {
+ mScrollState = SCROLL_WAITING_IN_ZONE;
+ mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
} else {
- drop(dragLayerX, dragLayerY);
+ mScrollState = SCROLL_OUTSIDE_ZONE;
}
- }
- endDrag();
- break;
- case MotionEvent.ACTION_CANCEL:
- mHandler.removeCallbacks(mScrollRunnable);
- cancelDrag();
- break;
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ mHandler.removeCallbacks(mScrollRunnable);
+ break;
}
- return true;
+ return mDragDriver.onTouchEvent(ev);
}
/**
@@ -642,7 +680,6 @@
public void prepareAccessibleDrag(int x, int y) {
mMotionDownX = x;
mMotionDownY = y;
- mLastDropTarget = null;
}
/**
@@ -660,7 +697,7 @@
dropTarget.prepareAccessibilityDrop();
// Perform the drop
- drop(location[0], location[1]);
+ drop(dropTarget, location[0], location[1], null);
endDrag();
}
@@ -690,49 +727,41 @@
return null;
}
- private void dropOnFlingToDeleteTarget(float x, float y, PointF vel) {
+ 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) {
@@ -825,10 +854,6 @@
mScrollView = v;
}
- DragView getDragView() {
- return mDragObject.dragView;
- }
-
private class ScrollRunnable implements Runnable {
private int mDirection;
diff --git a/src/com/android/launcher3/dragndrop/DragDriver.java b/src/com/android/launcher3/dragndrop/DragDriver.java
new file mode 100644
index 0000000..6e4b430
--- /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
similarity index 97%
rename from src/com/android/launcher3/DragLayer.java
rename to src/com/android/launcher3/dragndrop/DragLayer.java
index aaa14e6..741d5e6 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.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.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;
/**
@@ -159,7 +175,7 @@
}
private boolean isEventOverDropTargetBar(MotionEvent ev) {
- getDescendantRectRelativeToSelf(mLauncher.getSearchBar(), mHitRect);
+ getDescendantRectRelativeToSelf(mLauncher.getSearchDropTargetBar(), mHitRect);
if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) {
return true;
}
@@ -321,7 +337,7 @@
childrenForAccessibility.add(currentFolder);
if (isInAccessibleDrag()) {
- childrenForAccessibility.add(mLauncher.getSearchBar());
+ childrenForAccessibility.add(mLauncher.getSearchDropTargetBar());
}
} else {
super.addChildrenForAccessibility(childrenForAccessibility);
@@ -375,6 +391,11 @@
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 +785,7 @@
// 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) {
@@ -884,7 +905,7 @@
invalidate();
}
- void showPageHints() {
+ public void showPageHints() {
mShowPageHints = true;
Workspace workspace = mLauncher.getWorkspace();
getDescendantRectRelativeToSelf(workspace.getChildAt(workspace.numCustomPages()),
@@ -892,7 +913,7 @@
invalidate();
}
- void hidePageHints() {
+ public void hidePageHints() {
mShowPageHints = false;
invalidate();
}
diff --git a/src/com/android/launcher3/DragScroller.java b/src/com/android/launcher3/dragndrop/DragScroller.java
similarity index 96%
rename from src/com/android/launcher3/DragScroller.java
rename to src/com/android/launcher3/dragndrop/DragScroller.java
index e261f15..165d0b1 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
similarity index 82%
rename from src/com/android/launcher3/DragView.java
rename to src/com/android/launcher3/dragndrop/DragView.java
index dfa8202..88e11fa 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.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 @@
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 @@
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 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 @@
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();
}
}
});
@@ -131,7 +139,7 @@
measure(ms, ms);
mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
- if (Utilities.isLmpOrAbove()) {
+ if (Utilities.ATLEAST_LOLLIPOP) {
setElevation(getResources().getDimension(R.dimen.drag_elevation));
}
}
@@ -145,10 +153,6 @@
return mIntrinsicIconScale;
}
- public float getOffsetY() {
- return mOffsetY;
- }
-
public int getDragRegionLeft() {
return mDragRegion.left;
}
@@ -181,30 +185,33 @@
return mDragRegion;
}
- public float getInitialScale() {
- return mInitialScale;
- }
-
- public void updateInitialScaleToCurrentScale() {
- mInitialScale = getScaleX();
- }
-
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(mBitmap.getWidth(), mBitmap.getHeight());
}
+ // 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);
+ }
+
+ // 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 @@
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 @@
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mCrossFadeProgress = animation.getAnimatedFraction();
+ invalidate();
}
});
va.start();
@@ -252,14 +260,14 @@
setColorScale(color, m2);
m1.postConcat(m2);
- if (Utilities.isLmpOrAbove()) {
+ if (Utilities.ATLEAST_LOLLIPOP) {
animateFilterTo(m1.getArray());
} else {
mPaint.setColorFilter(new ColorMatrixColorFilter(m1));
invalidate();
}
} else {
- if (!Utilities.isLmpOrAbove() || mCurrentFilter == null) {
+ if (!Utilities.ATLEAST_LOLLIPOP || mCurrentFilter == null) {
mPaint.setColorFilter(null);
invalidate();
} else {
@@ -328,16 +336,12 @@
}
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 @@
* @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
similarity index 89%
rename from src/com/android/launcher3/SpringLoadedDragController.java
rename to src/com/android/launcher3/dragndrop/SpringLoadedDragController.java
index 45edaef..d7f41c9 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/AbstractUserComparator.java b/src/com/android/launcher3/model/AbstractUserComparator.java
index cf47ce6..bd28560 100644
--- a/src/com/android/launcher3/model/AbstractUserComparator.java
+++ b/src/com/android/launcher3/model/AbstractUserComparator.java
@@ -22,14 +22,12 @@
import com.android.launcher3.compat.UserManagerCompat;
import java.util.Comparator;
-import java.util.HashMap;
/**
* A comparator to arrange items based on user profiles.
*/
public abstract class AbstractUserComparator<T extends ItemInfo> implements Comparator<T> {
- private HashMap<UserHandleCompat, Long> mUserSerialCache = new HashMap<>();
private final UserManagerCompat mUserManager;
private final UserHandleCompat mMyUser;
@@ -43,25 +41,9 @@
if (mMyUser.equals(lhs.user)) {
return -1;
} else {
- Long aUserSerial = getAndCacheUserSerial(lhs.user);
- Long bUserSerial = getAndCacheUserSerial(rhs.user);
+ Long aUserSerial = mUserManager.getSerialNumberForUser(lhs.user);
+ Long bUserSerial = mUserManager.getSerialNumberForUser(rhs.user);
return aUserSerial.compareTo(bUserSerial);
}
}
-
- /**
- * Returns the user serial for this user, using a cached serial if possible.
- */
- private Long getAndCacheUserSerial(UserHandleCompat user) {
- Long userSerial = mUserSerialCache.get(user);
- if (userSerial == null) {
- userSerial = mUserManager.getSerialNumberForUser(user);
- mUserSerialCache.put(user, userSerial);
- }
- return userSerial;
- }
-
- public void clearUserCache() {
- mUserSerialCache.clear();
- }
}
diff --git a/src/com/android/launcher3/model/AppNameComparator.java b/src/com/android/launcher3/model/AppNameComparator.java
index c4b74d4..5f80037 100644
--- a/src/com/android/launcher3/model/AppNameComparator.java
+++ b/src/com/android/launcher3/model/AppNameComparator.java
@@ -68,8 +68,6 @@
* Returns a locale-aware comparator that will alphabetically order a list of applications.
*/
public Comparator<ItemInfo> getAppInfoComparator() {
- // Clear the user serial cache so that we get serials as needed in the comparator
- mAppInfoComparator.clearUserCache();
return mAppInfoComparator;
}
diff --git a/src/com/android/launcher3/model/MigrateFromRestoreTask.java b/src/com/android/launcher3/model/MigrateFromRestoreTask.java
new file mode 100644
index 0000000..6a529f6
--- /dev/null
+++ b/src/com/android/launcher3/model/MigrateFromRestoreTask.java
@@ -0,0 +1,767 @@
+package com.android.launcher3.model;
+
+import android.content.ComponentName;
+import android.content.ContentProviderOperation;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageInfo;
+import android.database.Cursor;
+import android.graphics.Point;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherProvider;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.PackageInstallerCompat;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.util.LongArrayMap;
+import com.android.launcher3.util.Thunk;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+
+/**
+ * This class takes care of shrinking the workspace (by maximum of one row and one column), as a
+ * result of restoring from a larger device.
+ */
+public class MigrateFromRestoreTask {
+
+ public static boolean ENABLED = false;
+
+ private static final String TAG = "MigrateFromRestoreTask";
+ private static final boolean DEBUG = true;
+
+ private static final String KEY_MIGRATION_SOURCE_SIZE = "migration_restore_src_size";
+ private static final String KEY_MIGRATION_WIDGET_MINSIZE = "migration_widget_min_size";
+
+ // These are carefully selected weights for various item types (Math.random?), to allow for
+ // the lease absurd migration experience.
+ private static final float WT_SHORTCUT = 1;
+ private static final float WT_APPLICATION = 0.8f;
+ private static final float WT_WIDGET_MIN = 2;
+ private static final float WT_WIDGET_FACTOR = 0.6f;
+ private static final float WT_FOLDER_FACTOR = 0.5f;
+
+ private final Context mContext;
+ private final ContentValues mTempValues = new ContentValues();
+ private final HashMap<String, Point> mWidgetMinSize;
+ private final InvariantDeviceProfile mIdp;
+
+ private HashSet<String> mValidPackages;
+ public ArrayList<Long> mEntryToRemove;
+ private ArrayList<ContentProviderOperation> mUpdateOperations;
+
+ private ArrayList<DbEntry> mCarryOver;
+
+ private final int mSrcX, mSrcY;
+ @Thunk final int mTrgX, mTrgY;
+ private final boolean mShouldRemoveX, mShouldRemoveY;
+
+ public MigrateFromRestoreTask(Context context) {
+ mContext = context;
+
+ SharedPreferences prefs = prefs(context);
+ Point sourceSize = parsePoint(prefs.getString(KEY_MIGRATION_SOURCE_SIZE, ""));
+ mSrcX = sourceSize.x;
+ mSrcY = sourceSize.y;
+
+ mWidgetMinSize = new HashMap<String, Point>();
+ for (String s : prefs.getStringSet(KEY_MIGRATION_WIDGET_MINSIZE,
+ Collections.<String>emptySet())) {
+ String[] parts = s.split("#");
+ mWidgetMinSize.put(parts[0], parsePoint(parts[1]));
+ }
+
+ mIdp = LauncherAppState.getInstance().getInvariantDeviceProfile();
+ mTrgX = mIdp.numColumns;
+ mTrgY = mIdp.numRows;
+ mShouldRemoveX = mTrgX < mSrcX;
+ mShouldRemoveY = mTrgY < mSrcY;
+ }
+
+ public void execute() throws Exception {
+ mEntryToRemove = new ArrayList<>();
+ mCarryOver = new ArrayList<>();
+ mUpdateOperations = new ArrayList<>();
+
+ // Initialize list of valid packages. This contain all the packages which are already on
+ // the device and packages which are being installed. Any item which doesn't belong to
+ // this set is removed.
+ // Since the loader removes such items anyway, removing these items here doesn't cause any
+ // extra data loss and gives us more free space on the grid for better migration.
+ mValidPackages = new HashSet<>();
+ for (PackageInfo info : mContext.getPackageManager().getInstalledPackages(0)) {
+ mValidPackages.add(info.packageName);
+ }
+ mValidPackages.addAll(PackageInstallerCompat.getInstance(mContext)
+ .updateAndGetActiveSessionCache().keySet());
+
+ ArrayList<Long> allScreens = LauncherModel.loadWorkspaceScreensDb(mContext);
+ if (allScreens.isEmpty()) {
+ throw new Exception("Unable to get workspace screens");
+ }
+
+ for (long screenId : allScreens) {
+ if (DEBUG) {
+ Log.d(TAG, "Migrating " + screenId);
+ }
+ migrateScreen(screenId);
+ }
+
+ if (!mCarryOver.isEmpty()) {
+ LongArrayMap<DbEntry> itemMap = new LongArrayMap<>();
+ for (DbEntry e : mCarryOver) {
+ itemMap.put(e.id, e);
+ }
+
+ do {
+ // Some items are still remaining. Try adding a few new screens.
+
+ // At every iteration, make sure that at least one item is removed from
+ // {@link #mCarryOver}, to prevent an infinite loop. If no item could be removed,
+ // break the loop and abort migration by throwing an exception.
+ OptimalPlacementSolution placement = new OptimalPlacementSolution(
+ new boolean[mTrgX][mTrgY], deepCopy(mCarryOver), true);
+ placement.find();
+ if (placement.finalPlacedItems.size() > 0) {
+ long newScreenId = LauncherAppState.getLauncherProvider().generateNewScreenId();
+ allScreens.add(newScreenId);
+ for (DbEntry item : placement.finalPlacedItems) {
+ if (!mCarryOver.remove(itemMap.get(item.id))) {
+ throw new Exception("Unable to find matching items");
+ }
+ item.screenId = newScreenId;
+ update(item);
+ }
+ } else {
+ throw new Exception("None of the items can be placed on an empty screen");
+ }
+
+ } while (!mCarryOver.isEmpty());
+
+
+ LauncherAppState.getInstance().getModel()
+ .updateWorkspaceScreenOrder(mContext, allScreens);
+ }
+
+ // Update items
+ mContext.getContentResolver().applyBatch(LauncherProvider.AUTHORITY, mUpdateOperations);
+
+ if (!mEntryToRemove.isEmpty()) {
+ if (DEBUG) {
+ Log.d(TAG, "Removing items: " + TextUtils.join(", ", mEntryToRemove));
+ }
+ mContext.getContentResolver().delete(LauncherSettings.Favorites.CONTENT_URI,
+ Utilities.createDbSelectionQuery(
+ LauncherSettings.Favorites._ID, mEntryToRemove), null);
+ }
+
+ // Make sure we haven't removed everything.
+ final Cursor c = mContext.getContentResolver().query(
+ LauncherSettings.Favorites.CONTENT_URI, null, null, null, null);
+ boolean hasData = c.moveToNext();
+ c.close();
+ if (!hasData) {
+ throw new Exception("Removed every thing during grid resize");
+ }
+ }
+
+ /**
+ * Migrate a particular screen id.
+ * Strategy:
+ * 1) For all possible combinations of row and column, pick the one which causes the least
+ * data loss: {@link #tryRemove(int, int, ArrayList, float[])}
+ * 2) Maintain a list of all lost items before this screen, and add any new item lost from
+ * this screen to that list as well.
+ * 3) If all those items from the above list can be placed on this screen, place them
+ * (otherwise they are placed on a new screen).
+ */
+ private void migrateScreen(long screenId) {
+ ArrayList<DbEntry> items = loadEntries(screenId);
+
+ int removedCol = Integer.MAX_VALUE;
+ int removedRow = Integer.MAX_VALUE;
+
+ // removeWt represents the cost function for loss of items during migration, and moveWt
+ // represents the cost function for repositioning the items. moveWt is only considered if
+ // removeWt is same for two different configurations.
+ // Start with Float.MAX_VALUE (assuming full data) and pick the configuration with least
+ // cost.
+ float removeWt = Float.MAX_VALUE;
+ float moveWt = Float.MAX_VALUE;
+ float[] outLoss = new float[2];
+ ArrayList<DbEntry> finalItems = null;
+
+ // Try removing all possible combinations
+ for (int x = 0; x < mSrcX; x++) {
+ for (int y = 0; y < mSrcY; y++) {
+ // Use a deep copy when trying out a particular combination as it can change
+ // the underlying object.
+ ArrayList<DbEntry> itemsOnScreen = tryRemove(x, y, deepCopy(items), outLoss);
+
+ if ((outLoss[0] < removeWt) || ((outLoss[0] == removeWt) && (outLoss[1] < moveWt))) {
+ removeWt = outLoss[0];
+ moveWt = outLoss[1];
+ removedCol = mShouldRemoveX ? x : removedCol;
+ removedRow = mShouldRemoveY ? y : removedRow;
+ finalItems = itemsOnScreen;
+ }
+
+ // No need to loop over all rows, if a row removal is not needed.
+ if (!mShouldRemoveY) {
+ break;
+ }
+ }
+
+ if (!mShouldRemoveX) {
+ break;
+ }
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, String.format("Removing row %d, column %d on screen %d",
+ removedRow, removedCol, screenId));
+ }
+
+ LongArrayMap<DbEntry> itemMap = new LongArrayMap<>();
+ for (DbEntry e : deepCopy(items)) {
+ itemMap.put(e.id, e);
+ }
+
+ for (DbEntry item : finalItems) {
+ DbEntry org = itemMap.get(item.id);
+ itemMap.remove(item.id);
+
+ // Check if update is required
+ if (!item.columnsSame(org)) {
+ update(item);
+ }
+ }
+
+ // The remaining items in {@link #itemMap} are those which didn't get placed.
+ for (DbEntry item : itemMap) {
+ mCarryOver.add(item);
+ }
+
+ if (!mCarryOver.isEmpty() && removeWt == 0) {
+ // No new items were removed in this step. Try placing all the items on this screen.
+ boolean[][] occupied = new boolean[mTrgX][mTrgY];
+ for (DbEntry item : finalItems) {
+ markCells(occupied, item, true);
+ }
+
+ OptimalPlacementSolution placement = new OptimalPlacementSolution(occupied,
+ deepCopy(mCarryOver), true);
+ placement.find();
+ if (placement.lowestWeightLoss == 0) {
+ // All items got placed
+
+ for (DbEntry item : placement.finalPlacedItems) {
+ item.screenId = screenId;
+ update(item);
+ }
+
+ mCarryOver.clear();
+ }
+ }
+ }
+
+ /**
+ * Updates an item in the DB.
+ */
+ private void update(DbEntry item) {
+ mTempValues.clear();
+ item.addToContentValues(mTempValues);
+ mUpdateOperations.add(ContentProviderOperation
+ .newUpdate(LauncherSettings.Favorites.getContentUri(item.id))
+ .withValues(mTempValues).build());
+ }
+
+ /**
+ * Tries the remove the provided row and column.
+ * @param items all the items on the screen under operation
+ * @param outLoss array of size 2. The first entry is filled with weight loss, and the second
+ * with the overall item movement.
+ */
+ private ArrayList<DbEntry> tryRemove(int col, int row, ArrayList<DbEntry> items,
+ float[] outLoss) {
+ boolean[][] occupied = new boolean[mTrgX][mTrgY];
+
+ col = mShouldRemoveX ? col : Integer.MAX_VALUE;
+ row = mShouldRemoveY ? row : Integer.MAX_VALUE;
+
+ ArrayList<DbEntry> finalItems = new ArrayList<>();
+ ArrayList<DbEntry> removedItems = new ArrayList<>();
+
+ for (DbEntry item : items) {
+ if ((item.cellX <= col && (item.spanX + item.cellX) > col)
+ || (item.cellY <= row && (item.spanY + item.cellY) > row)) {
+ removedItems.add(item);
+ if (item.cellX >= col) item.cellX --;
+ if (item.cellY >= row) item.cellY --;
+ } else {
+ if (item.cellX > col) item.cellX --;
+ if (item.cellY > row) item.cellY --;
+ finalItems.add(item);
+ markCells(occupied, item, true);
+ }
+ }
+
+ OptimalPlacementSolution placement = new OptimalPlacementSolution(occupied, removedItems);
+ placement.find();
+ finalItems.addAll(placement.finalPlacedItems);
+ outLoss[0] = placement.lowestWeightLoss;
+ outLoss[1] = placement.lowestMoveCost;
+ return finalItems;
+ }
+
+ @Thunk void markCells(boolean[][] occupied, DbEntry item, boolean val) {
+ for (int i = item.cellX; i < (item.cellX + item.spanX); i++) {
+ for (int j = item.cellY; j < (item.cellY + item.spanY); j++) {
+ occupied[i][j] = val;
+ }
+ }
+ }
+
+ @Thunk boolean isVacant(boolean[][] occupied, int x, int y, int w, int h) {
+ if (x + w > mTrgX) return false;
+ if (y + h > mTrgY) return false;
+
+ for (int i = 0; i < w; i++) {
+ for (int j = 0; j < h; j++) {
+ if (occupied[i + x][j + y]) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ private class OptimalPlacementSolution {
+ private final ArrayList<DbEntry> itemsToPlace;
+ private final boolean[][] occupied;
+
+ // If set to true, item movement are not considered in move cost, leading to a more
+ // linear placement.
+ private final boolean ignoreMove;
+
+ float lowestWeightLoss = Float.MAX_VALUE;
+ float lowestMoveCost = Float.MAX_VALUE;
+ ArrayList<DbEntry> finalPlacedItems;
+
+ public OptimalPlacementSolution(boolean[][] occupied, ArrayList<DbEntry> itemsToPlace) {
+ this(occupied, itemsToPlace, false);
+ }
+
+ public OptimalPlacementSolution(boolean[][] occupied, ArrayList<DbEntry> itemsToPlace,
+ boolean ignoreMove) {
+ this.occupied = occupied;
+ this.itemsToPlace = itemsToPlace;
+ this.ignoreMove = ignoreMove;
+
+ // Sort the items such that larger widgets appear first followed by 1x1 items
+ Collections.sort(this.itemsToPlace);
+ }
+
+ public void find() {
+ find(0, 0, 0, new ArrayList<DbEntry>());
+ }
+
+ /**
+ * Recursively finds a placement for the provided items.
+ * @param index the position in {@link #itemsToPlace} to start looking at.
+ * @param weightLoss total weight loss upto this point
+ * @param moveCost total move cost upto this point
+ * @param itemsPlaced all the items already placed upto this point
+ */
+ public void find(int index, float weightLoss, float moveCost,
+ ArrayList<DbEntry> itemsPlaced) {
+ if ((weightLoss >= lowestWeightLoss) ||
+ ((weightLoss == lowestWeightLoss) && (moveCost >= lowestMoveCost))) {
+ // Abort, as we already have a better solution.
+ return;
+
+ } else if (index >= itemsToPlace.size()) {
+ // End loop.
+ lowestWeightLoss = weightLoss;
+ lowestMoveCost = moveCost;
+
+ // Keep a deep copy of current configuration as it can change during recursion.
+ finalPlacedItems = deepCopy(itemsPlaced);
+ return;
+ }
+
+ DbEntry me = itemsToPlace.get(index);
+ int myX = me.cellX;
+ int myY = me.cellY;
+
+ // List of items to pass over if this item was placed.
+ ArrayList<DbEntry> itemsIncludingMe = new ArrayList<>(itemsPlaced.size() + 1);
+ itemsIncludingMe.addAll(itemsPlaced);
+ itemsIncludingMe.add(me);
+
+ if (me.spanX > 1 || me.spanY > 1) {
+ // If the current item is a widget (and it greater than 1x1), try to place it at
+ // all possible positions. This is because a widget placed at one position can
+ // affect the placement of a different widget.
+ int myW = me.spanX;
+ int myH = me.spanY;
+
+ for (int y = 0; y < mTrgY; y++) {
+ for (int x = 0; x < mTrgX; x++) {
+ float newMoveCost = moveCost;
+ if (x != myX) {
+ me.cellX = x;
+ newMoveCost ++;
+ }
+ if (y != myY) {
+ me.cellY = y;
+ newMoveCost ++;
+ }
+ if (ignoreMove) {
+ newMoveCost = moveCost;
+ }
+
+ if (isVacant(occupied, x, y, myW, myH)) {
+ // place at this position and continue search.
+ markCells(occupied, me, true);
+ find(index + 1, weightLoss, newMoveCost, itemsIncludingMe);
+ markCells(occupied, me, false);
+ }
+
+ // Try resizing horizontally
+ if (myW > me.minSpanX && isVacant(occupied, x, y, myW - 1, myH)) {
+ me.spanX --;
+ markCells(occupied, me, true);
+ // 1 extra move cost
+ find(index + 1, weightLoss, newMoveCost + 1, itemsIncludingMe);
+ markCells(occupied, me, false);
+ me.spanX ++;
+ }
+
+ // Try resizing vertically
+ if (myH > me.minSpanY && isVacant(occupied, x, y, myW, myH - 1)) {
+ me.spanY --;
+ markCells(occupied, me, true);
+ // 1 extra move cost
+ find(index + 1, weightLoss, newMoveCost + 1, itemsIncludingMe);
+ markCells(occupied, me, false);
+ me.spanY ++;
+ }
+
+ // Try resizing horizontally & vertically
+ if (myH > me.minSpanY && myW > me.minSpanX &&
+ isVacant(occupied, x, y, myW - 1, myH - 1)) {
+ me.spanX --;
+ me.spanY --;
+ markCells(occupied, me, true);
+ // 2 extra move cost
+ find(index + 1, weightLoss, newMoveCost + 2, itemsIncludingMe);
+ markCells(occupied, me, false);
+ me.spanX ++;
+ me.spanY ++;
+ }
+ me.cellX = myX;
+ me.cellY = myY;
+ }
+ }
+
+ // Finally also try a solution when this item is not included. Trying it in the end
+ // causes it to get skipped in most cases due to higher weight loss, and prevents
+ // unnecessary deep copies of various configurations.
+ find(index + 1, weightLoss + me.weight, moveCost, itemsPlaced);
+ } else {
+ // Since this is a 1x1 item and all the following items are also 1x1, just place
+ // it at 'the most appropriate position' and hope for the best.
+ // The most appropriate position: one with lease straight line distance
+ int newDistance = Integer.MAX_VALUE;
+ int newX = Integer.MAX_VALUE, newY = Integer.MAX_VALUE;
+
+ for (int y = 0; y < mTrgY; y++) {
+ for (int x = 0; x < mTrgX; x++) {
+ if (!occupied[x][y]) {
+ int dist = ignoreMove ? 0 :
+ ((me.cellX - x) * (me.cellX - x) + (me.cellY - y) * (me.cellY - y));
+ if (dist < newDistance) {
+ newX = x;
+ newY = y;
+ newDistance = dist;
+ }
+ }
+ }
+ }
+
+ if (newX < mTrgX && newY < mTrgY) {
+ float newMoveCost = moveCost;
+ if (newX != myX) {
+ me.cellX = newX;
+ newMoveCost ++;
+ }
+ if (newY != myY) {
+ me.cellY = newY;
+ newMoveCost ++;
+ }
+ if (ignoreMove) {
+ newMoveCost = moveCost;
+ }
+ markCells(occupied, me, true);
+ find(index + 1, weightLoss, newMoveCost, itemsIncludingMe);
+ markCells(occupied, me, false);
+ me.cellX = myX;
+ me.cellY = myY;
+
+ // Try to find a solution without this item, only if
+ // 1) there was at least one space, i.e., we were able to place this item
+ // 2) if the next item has the same weight (all items are already sorted), as
+ // if it has lower weight, that solution will automatically get discarded.
+ // 3) ignoreMove false otherwise, move cost is ignored and the weight will
+ // anyway be same.
+ if (index + 1 < itemsToPlace.size()
+ && itemsToPlace.get(index + 1).weight >= me.weight && !ignoreMove) {
+ find(index + 1, weightLoss + me.weight, moveCost, itemsPlaced);
+ }
+ } else {
+ // No more space. Jump to the end.
+ for (int i = index + 1; i < itemsToPlace.size(); i++) {
+ weightLoss += itemsToPlace.get(i).weight;
+ }
+ find(itemsToPlace.size(), weightLoss + me.weight, moveCost, itemsPlaced);
+ }
+ }
+ }
+ }
+
+ /**
+ * Loads entries for a particular screen id.
+ */
+ public ArrayList<DbEntry> loadEntries(long screen) {
+ Cursor c = mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
+ new String[] {
+ Favorites._ID, // 0
+ Favorites.ITEM_TYPE, // 1
+ Favorites.CELLX, // 2
+ Favorites.CELLY, // 3
+ Favorites.SPANX, // 4
+ Favorites.SPANY, // 5
+ Favorites.INTENT, // 6
+ Favorites.APPWIDGET_PROVIDER}, // 7
+ Favorites.CONTAINER + " = " + Favorites.CONTAINER_DESKTOP
+ + " AND " + Favorites.SCREEN + " = " + screen, null, null, null);
+
+ final int indexId = c.getColumnIndexOrThrow(Favorites._ID);
+ final int indexItemType = c.getColumnIndexOrThrow(Favorites.ITEM_TYPE);
+ final int indexCellX = c.getColumnIndexOrThrow(Favorites.CELLX);
+ final int indexCellY = c.getColumnIndexOrThrow(Favorites.CELLY);
+ final int indexSpanX = c.getColumnIndexOrThrow(Favorites.SPANX);
+ final int indexSpanY = c.getColumnIndexOrThrow(Favorites.SPANY);
+ final int indexIntent = c.getColumnIndexOrThrow(Favorites.INTENT);
+ final int indexAppWidgetProvider = c.getColumnIndexOrThrow(Favorites.APPWIDGET_PROVIDER);
+
+ ArrayList<DbEntry> entries = new ArrayList<>();
+ while (c.moveToNext()) {
+ DbEntry entry = new DbEntry();
+ entry.id = c.getLong(indexId);
+ entry.itemType = c.getInt(indexItemType);
+ entry.cellX = c.getInt(indexCellX);
+ entry.cellY = c.getInt(indexCellY);
+ entry.spanX = c.getInt(indexSpanX);
+ entry.spanY = c.getInt(indexSpanY);
+ entry.screenId = screen;
+
+ try {
+ // calculate weight
+ switch (entry.itemType) {
+ case Favorites.ITEM_TYPE_SHORTCUT:
+ case Favorites.ITEM_TYPE_APPLICATION: {
+ verifyIntent(c.getString(indexIntent));
+ entry.weight = entry.itemType == Favorites.ITEM_TYPE_SHORTCUT
+ ? WT_SHORTCUT : WT_APPLICATION;
+ break;
+ }
+ case Favorites.ITEM_TYPE_APPWIDGET: {
+ String provider = c.getString(indexAppWidgetProvider);
+ ComponentName cn = ComponentName.unflattenFromString(provider);
+ verifyPackage(cn.getPackageName());
+ entry.weight = Math.max(WT_WIDGET_MIN, WT_WIDGET_FACTOR
+ * entry.spanX * entry.spanY);
+
+ // Migration happens for current user only.
+ LauncherAppWidgetProviderInfo pInfo = LauncherModel.getProviderInfo(
+ mContext, cn, UserHandleCompat.myUserHandle());
+ Point spans = pInfo == null ?
+ mWidgetMinSize.get(provider) : pInfo.getMinSpans(mIdp, mContext);
+ if (spans != null) {
+ entry.minSpanX = spans.x > 0 ? spans.x : entry.spanX;
+ entry.minSpanY = spans.y > 0 ? spans.y : entry.spanY;
+ } else {
+ // Assume that the widget be resized down to 2x2
+ entry.minSpanX = entry.minSpanY = 2;
+ }
+
+ if (entry.minSpanX > mTrgX || entry.minSpanY > mTrgY) {
+ throw new Exception("Widget can't be resized down to fit the grid");
+ }
+ break;
+ }
+ case Favorites.ITEM_TYPE_FOLDER: {
+ int total = getFolderItemsCount(entry.id);
+ if (total == 0) {
+ throw new Exception("Folder is empty");
+ }
+ entry.weight = WT_FOLDER_FACTOR * total;
+ break;
+ }
+ default:
+ throw new Exception("Invalid item type");
+ }
+ } catch (Exception e) {
+ if (DEBUG) {
+ Log.d(TAG, "Removing item " + entry.id, e);
+ }
+ mEntryToRemove.add(entry.id);
+ continue;
+ }
+
+ entries.add(entry);
+ }
+ return entries;
+ }
+
+ /**
+ * @return the number of valid items in the folder.
+ */
+ private int getFolderItemsCount(long folderId) {
+ Cursor c = mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
+ new String[] {Favorites._ID, Favorites.INTENT},
+ Favorites.CONTAINER + " = " + folderId, null, null, null);
+
+ int total = 0;
+ while (c.moveToNext()) {
+ try {
+ verifyIntent(c.getString(1));
+ total++;
+ } catch (Exception e) {
+ mEntryToRemove.add(c.getLong(0));
+ }
+ }
+
+ return total;
+ }
+
+ /**
+ * Verifies if the intent should be restored.
+ */
+ private void verifyIntent(String intentStr) throws Exception {
+ Intent intent = Intent.parseUri(intentStr, 0);
+ if (intent.getComponent() != null) {
+ verifyPackage(intent.getComponent().getPackageName());
+ } else if (intent.getPackage() != null) {
+ // Only verify package if the component was null.
+ verifyPackage(intent.getPackage());
+ }
+ }
+
+ /**
+ * Verifies if the package should be restored
+ */
+ private void verifyPackage(String packageName) throws Exception {
+ if (!mValidPackages.contains(packageName)) {
+ throw new Exception("Package not available");
+ }
+ }
+
+ private static class DbEntry extends ItemInfo implements Comparable<DbEntry> {
+
+ public float weight;
+
+ public DbEntry() { }
+
+ public DbEntry copy() {
+ DbEntry entry = new DbEntry();
+ entry.copyFrom(this);
+ entry.weight = weight;
+ entry.minSpanX = minSpanX;
+ entry.minSpanY = minSpanY;
+ return entry;
+ }
+
+ /**
+ * Comparator such that larger widgets come first, followed by all 1x1 items
+ * based on their weights.
+ */
+ @Override
+ public int compareTo(DbEntry another) {
+ if (itemType == Favorites.ITEM_TYPE_APPWIDGET) {
+ if (another.itemType == Favorites.ITEM_TYPE_APPWIDGET) {
+ return another.spanY * another.spanX - spanX * spanY;
+ } else {
+ return -1;
+ }
+ } else if (another.itemType == Favorites.ITEM_TYPE_APPWIDGET) {
+ return 1;
+ } else {
+ // Place higher weight before lower weight.
+ return Float.compare(another.weight, weight);
+ }
+ }
+
+ public boolean columnsSame(DbEntry org) {
+ return org.cellX == cellX && org.cellY == cellY && org.spanX == spanX &&
+ org.spanY == spanY && org.screenId == screenId;
+ }
+
+ public void addToContentValues(ContentValues values) {
+ values.put(LauncherSettings.Favorites.SCREEN, screenId);
+ values.put(LauncherSettings.Favorites.CELLX, cellX);
+ values.put(LauncherSettings.Favorites.CELLY, cellY);
+ values.put(LauncherSettings.Favorites.SPANX, spanX);
+ values.put(LauncherSettings.Favorites.SPANY, spanY);
+ }
+ }
+
+ @Thunk static ArrayList<DbEntry> deepCopy(ArrayList<DbEntry> src) {
+ ArrayList<DbEntry> dup = new ArrayList<DbEntry>(src.size());
+ for (DbEntry e : src) {
+ dup.add(e.copy());
+ }
+ return dup;
+ }
+
+ private static Point parsePoint(String point) {
+ String[] split = point.split(",");
+ return new Point(Integer.parseInt(split[0]), Integer.parseInt(split[1]));
+ }
+
+ public static void markForMigration(Context context, int srcX, int srcY,
+ HashSet<String> widgets) {
+ prefs(context).edit()
+ .putString(KEY_MIGRATION_SOURCE_SIZE, srcX + "," + srcY)
+ .putStringSet(KEY_MIGRATION_WIDGET_MINSIZE, widgets)
+ .apply();
+ }
+
+ public static boolean shouldRunTask(Context context) {
+ return !TextUtils.isEmpty(prefs(context).getString(KEY_MIGRATION_SOURCE_SIZE, ""));
+ }
+
+ public static void clearFlags(Context context) {
+ prefs(context).edit().remove(KEY_MIGRATION_SOURCE_SIZE)
+ .remove(KEY_MIGRATION_WIDGET_MINSIZE).commit();
+ }
+
+ private static SharedPreferences prefs(Context context) {
+ return context.getSharedPreferences(LauncherAppState.getSharedPreferencesKey(),
+ Context.MODE_PRIVATE);
+ }
+}
diff --git a/src/com/android/launcher3/model/WidgetsAndShortcutNameComparator.java b/src/com/android/launcher3/model/WidgetsAndShortcutNameComparator.java
index 61e8952..b990560 100644
--- a/src/com/android/launcher3/model/WidgetsAndShortcutNameComparator.java
+++ b/src/com/android/launcher3/model/WidgetsAndShortcutNameComparator.java
@@ -1,13 +1,14 @@
package com.android.launcher3.model;
+import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.Utilities;
import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.util.ComponentKey;
import java.text.Collator;
import java.util.Comparator;
@@ -16,53 +17,81 @@
public class WidgetsAndShortcutNameComparator implements Comparator<Object> {
private final AppWidgetManagerCompat mManager;
private final PackageManager mPackageManager;
- private final HashMap<Object, String> mLabelCache;
+ private final HashMap<ComponentKey, String> mLabelCache;
private final Collator mCollator;
private final UserHandleCompat mMainHandle;
public WidgetsAndShortcutNameComparator(Context context) {
mManager = AppWidgetManagerCompat.getInstance(context);
mPackageManager = context.getPackageManager();
- mLabelCache = new HashMap<Object, String>();
+ mLabelCache = new HashMap<>();
mCollator = Collator.getInstance();
mMainHandle = UserHandleCompat.myUserHandle();
}
+ /**
+ * Resets any stored state.
+ */
+ public void reset() {
+ mLabelCache.clear();
+ }
+
@Override
- public final int compare(Object a, Object b) {
- String labelA, labelB;
- if (mLabelCache.containsKey(a)) {
- labelA = mLabelCache.get(a);
- } else {
- labelA = (a instanceof LauncherAppWidgetProviderInfo)
- ? Utilities.trim(mManager.loadLabel((LauncherAppWidgetProviderInfo) a))
- : Utilities.trim(((ResolveInfo) a).loadLabel(mPackageManager));
- mLabelCache.put(a, labelA);
- }
- if (mLabelCache.containsKey(b)) {
- labelB = mLabelCache.get(b);
- } else {
- labelB = (b instanceof LauncherAppWidgetProviderInfo)
- ? Utilities.trim(mManager.loadLabel((LauncherAppWidgetProviderInfo) b))
- : Utilities.trim(((ResolveInfo) b).loadLabel(mPackageManager));
- mLabelCache.put(b, labelB);
- }
-
- // Currently, there is no work profile shortcuts, hence only considering the widget cases.
-
- boolean aWorkProfile = (a instanceof LauncherAppWidgetProviderInfo) &&
- !mMainHandle.equals(mManager.getUser((LauncherAppWidgetProviderInfo) a));
- boolean bWorkProfile = (b instanceof LauncherAppWidgetProviderInfo) &&
- !mMainHandle.equals(mManager.getUser((LauncherAppWidgetProviderInfo) b));
+ public final int compare(Object objA, Object objB) {
+ ComponentKey keyA = getComponentKey(objA);
+ ComponentKey keyB = getComponentKey(objB);
// Independent of how the labels compare, if only one of the two widget info belongs to
// work profile, put that one in the back.
+ boolean aWorkProfile = !mMainHandle.equals(keyA.user);
+ boolean bWorkProfile = !mMainHandle.equals(keyB.user);
if (aWorkProfile && !bWorkProfile) {
return 1;
}
if (!aWorkProfile && bWorkProfile) {
return -1;
}
+
+ // Get the labels for comparison
+ String labelA = mLabelCache.get(keyA);
+ String labelB = mLabelCache.get(keyB);
+ if (labelA == null) {
+ labelA = getLabel(objA);
+ mLabelCache.put(keyA, labelA);
+ }
+ if (labelB == null) {
+ labelB = getLabel(objB);
+ mLabelCache.put(keyB, labelB);
+ }
return mCollator.compare(labelA, labelB);
}
+
+ /**
+ * @return a component key for the given widget or shortcut info.
+ */
+ private ComponentKey getComponentKey(Object o) {
+ if (o instanceof LauncherAppWidgetProviderInfo) {
+ LauncherAppWidgetProviderInfo widgetInfo = (LauncherAppWidgetProviderInfo) o;
+ return new ComponentKey(widgetInfo.provider, mManager.getUser(widgetInfo));
+ } else {
+ ResolveInfo shortcutInfo = (ResolveInfo) o;
+ ComponentName cn = new ComponentName(shortcutInfo.activityInfo.packageName,
+ shortcutInfo.activityInfo.name);
+ // Currently, there are no work profile shortcuts
+ return new ComponentKey(cn, UserHandleCompat.myUserHandle());
+ }
+ }
+
+ /**
+ * @return the label for the given widget or shortcut info. This may be an expensive call.
+ */
+ private String getLabel(Object o) {
+ if (o instanceof LauncherAppWidgetProviderInfo) {
+ LauncherAppWidgetProviderInfo widgetInfo = (LauncherAppWidgetProviderInfo) o;
+ return Utilities.trim(mManager.loadLabel(widgetInfo));
+ } else {
+ ResolveInfo shortcutInfo = (ResolveInfo) o;
+ return Utilities.trim(shortcutInfo.loadLabel(mPackageManager));
+ }
+ }
};
diff --git a/src/com/android/launcher3/model/WidgetsModel.java b/src/com/android/launcher3/model/WidgetsModel.java
index 185dfca..eef4f91 100644
--- a/src/com/android/launcher3/model/WidgetsModel.java
+++ b/src/com/android/launcher3/model/WidgetsModel.java
@@ -8,6 +8,9 @@
import com.android.launcher3.AppFilter;
import com.android.launcher3.IconCache;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.Utilities;
import com.android.launcher3.compat.AlphabeticIndexCompat;
@@ -39,8 +42,8 @@
private ArrayList<Object> mRawList;
private final AppWidgetManagerCompat mAppWidgetMgr;
- private final Comparator mWidgetAndShortcutNameComparator;
- private final Comparator mAppNameComparator;
+ private final WidgetsAndShortcutNameComparator mWidgetAndShortcutNameComparator;
+ private final Comparator<ItemInfo> mAppNameComparator;
private final IconCache mIconCache;
private final AppFilter mAppFilter;
private AlphabeticIndexCompat mIndexer;
@@ -54,6 +57,7 @@
mIndexer = new AlphabeticIndexCompat(context);
}
+ @SuppressWarnings("unchecked")
private WidgetsModel(WidgetsModel model) {
mAppWidgetMgr = model.mAppWidgetMgr;
mPackageItemInfos = (ArrayList<PackageItemInfo>) model.mPackageItemInfos.clone();
@@ -103,6 +107,9 @@
// clear the lists.
mWidgetsList.clear();
mPackageItemInfos.clear();
+ mWidgetAndShortcutNameComparator.reset();
+
+ InvariantDeviceProfile idp = LauncherAppState.getInstance().getInvariantDeviceProfile();
// add and update.
for (Object o: rawWidgetsShortcuts) {
@@ -111,9 +118,23 @@
ComponentName componentName = null;
if (o instanceof LauncherAppWidgetProviderInfo) {
LauncherAppWidgetProviderInfo widgetInfo = (LauncherAppWidgetProviderInfo) o;
- componentName = widgetInfo.provider;
- packageName = widgetInfo.provider.getPackageName();
- userHandle = mAppWidgetMgr.getUser(widgetInfo);
+
+ // Ensure that all widgets we show can be added on a workspace of this size
+ int minSpanX = Math.min(widgetInfo.spanX, widgetInfo.minSpanX);
+ int minSpanY = Math.min(widgetInfo.spanY, widgetInfo.minSpanY);
+ if (minSpanX <= (int) idp.numColumns &&
+ minSpanY <= (int) idp.numRows) {
+ componentName = widgetInfo.provider;
+ packageName = widgetInfo.provider.getPackageName();
+ userHandle = mAppWidgetMgr.getUser(widgetInfo);
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, String.format(
+ "Widget %s : (%d X %d) can't fit on this device",
+ widgetInfo.provider, minSpanX, minSpanY));
+ }
+ continue;
+ }
} else if (o instanceof ResolveInfo) {
ResolveInfo resolveInfo = (ResolveInfo) o;
componentName = new ComponentName(resolveInfo.activityInfo.packageName,
@@ -139,7 +160,7 @@
if (widgetsShortcutsList != null) {
widgetsShortcutsList.add(o);
} else {
- widgetsShortcutsList = new ArrayList<Object>();
+ widgetsShortcutsList = new ArrayList<>();
widgetsShortcutsList.add(o);
pInfo = new PackageItemInfo(packageName);
mIconCache.getTitleAndIconForApp(packageName, userHandle,
diff --git a/src/com/android/launcher3/testing/LauncherExtension.java b/src/com/android/launcher3/testing/LauncherExtension.java
index 34492e4..b4d7459 100644
--- a/src/com/android/launcher3/testing/LauncherExtension.java
+++ b/src/com/android/launcher3/testing/LauncherExtension.java
@@ -202,6 +202,11 @@
}
@Override
+ public boolean startSearchFromAllApps(String query) {
+ return false;
+ }
+
+ @Override
public void startVoice() {
}
@@ -330,7 +335,8 @@
@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/util/ComponentKey.java b/src/com/android/launcher3/util/ComponentKey.java
index 6a7df43..b7aafae 100644
--- a/src/com/android/launcher3/util/ComponentKey.java
+++ b/src/com/android/launcher3/util/ComponentKey.java
@@ -64,8 +64,11 @@
* Encodes a component key as a string of the form [flattenedComponentString#userId].
*/
public String flattenToString(Context context) {
- return componentName.flattenToString() + "#" +
- UserManagerCompat.getInstance(context).getSerialNumberForUser(user);
+ String flattened = componentName.flattenToString();
+ if (user != null) {
+ flattened += "#" + UserManagerCompat.getInstance(context).getSerialNumberForUser(user);
+ }
+ return flattened;
}
@Override
diff --git a/src/com/android/launcher3/util/FlingAnimation.java b/src/com/android/launcher3/util/FlingAnimation.java
index 55c5d7d..f82038b 100644
--- a/src/com/android/launcher3/util/FlingAnimation.java
+++ b/src/com/android/launcher3/util/FlingAnimation.java
@@ -7,9 +7,9 @@
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 {
diff --git a/src/com/android/launcher3/util/FocusLogic.java b/src/com/android/launcher3/util/FocusLogic.java
index 696eabe..1c6efbc 100644
--- a/src/com/android/launcher3/util/FocusLogic.java
+++ b/src/com/android/launcher3/util/FocusLogic.java
@@ -22,9 +22,6 @@
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/ManagedProfileHeuristic.java b/src/com/android/launcher3/util/ManagedProfileHeuristic.java
index b37f447..74fc92a 100644
--- a/src/com/android/launcher3/util/ManagedProfileHeuristic.java
+++ b/src/com/android/launcher3/util/ManagedProfileHeuristic.java
@@ -68,7 +68,7 @@
private static final long AUTO_ADD_TO_FOLDER_DURATION = 8 * 60 * 60 * 1000;
public static ManagedProfileHeuristic get(Context context, UserHandleCompat user) {
- if (Utilities.isLmpOrAbove() && !UserHandleCompat.myUserHandle().equals(user)) {
+ if (Utilities.ATLEAST_LOLLIPOP && !UserHandleCompat.myUserHandle().equals(user)) {
return new ManagedProfileHeuristic(context, user);
}
return null;
@@ -296,7 +296,7 @@
* Verifies that entries corresponding to {@param users} exist and removes all invalid entries.
*/
public static void processAllUsers(List<UserHandleCompat> users, Context context) {
- if (!Utilities.isLmpOrAbove()) {
+ if (!Utilities.ATLEAST_LOLLIPOP) {
return;
}
UserManagerCompat userManager = UserManagerCompat.getInstance(context);
diff --git a/src/com/android/launcher3/util/UiThreadCircularReveal.java b/src/com/android/launcher3/util/UiThreadCircularReveal.java
index 3ca3aee..f2b5e5e 100644
--- a/src/com/android/launcher3/util/UiThreadCircularReveal.java
+++ b/src/com/android/launcher3/util/UiThreadCircularReveal.java
@@ -47,7 +47,7 @@
float progress = arg0.getAnimatedFraction();
outlineProvider.setProgress(progress);
revealView.invalidateOutline();
- if (!Utilities.isLmpMR1OrAbove()) {
+ if (!Utilities.ATLEAST_LOLLIPOP_MR1) {
revealView.invalidate();
}
}
diff --git a/src/com/android/launcher3/util/WallpaperUtils.java b/src/com/android/launcher3/util/WallpaperUtils.java
index 53b2acd..a5251e1 100644
--- a/src/com/android/launcher3/util/WallpaperUtils.java
+++ b/src/com/android/launcher3/util/WallpaperUtils.java
@@ -17,13 +17,18 @@
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;
+
/**
* Utility methods for wallpaper management.
*/
@@ -33,28 +38,59 @@
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);
+ 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());
+
+ 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);
- int savedWidth = sharedPrefs.getInt(WALLPAPER_WIDTH_KEY, -1);
- int savedHeight = sharedPrefs.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);
}
}
@@ -62,7 +98,7 @@
* 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
@@ -92,19 +128,10 @@
@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 (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_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/PendingAddWidgetInfo.java b/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
index 758287a..fcb714f 100644
--- a/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
+++ b/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
@@ -31,10 +31,6 @@
* @see {@link PendingAddItemInfo}
*/
public class PendingAddWidgetInfo extends PendingAddItemInfo {
- public int minWidth;
- public int minHeight;
- public int minResizeWidth;
- public int minResizeHeight;
public int previewImage;
public int icon;
public LauncherAppWidgetProviderInfo info;
@@ -50,17 +46,13 @@
this.info = i;
user = AppWidgetManagerCompat.getInstance(launcher).getUser(i);
componentName = i.provider;
- minWidth = i.minWidth;
- minHeight = i.minHeight;
- minResizeWidth = i.minResizeWidth;
- minResizeHeight = i.minResizeHeight;
previewImage = i.previewImage;
icon = i.icon;
- spanX = i.getSpanX(launcher);
- spanY = i.getSpanY(launcher);
- minSpanX = i.getMinSpanX(launcher);
- minSpanY = i.getMinSpanY(launcher);
+ spanX = i.spanX;
+ spanY = i.spanY;
+ minSpanX = i.minSpanX;
+ minSpanY = i.minSpanY;
}
public boolean isCustomWidget() {
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index 7496ea2..94bbd92 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -146,8 +146,8 @@
mInfo = info;
// TODO(hyunyoungs): setup a cache for these labels.
mWidgetName.setText(AppWidgetManagerCompat.getInstance(getContext()).loadLabel(info));
- int hSpan = Math.min(info.getSpanX(mLauncher), profile.numColumns);
- int vSpan = Math.min(info.getSpanY(mLauncher), profile.numRows);
+ int hSpan = Math.min(info.spanX, profile.numColumns);
+ int vSpan = Math.min(info.spanY, profile.numRows);
mWidgetDims.setText(String.format(mDimensionsFormatString, hSpan, vSpan));
mWidgetPreviewLoader = loader;
}
diff --git a/src/com/android/launcher3/widget/WidgetHostViewLoader.java b/src/com/android/launcher3/widget/WidgetHostViewLoader.java
index bdd117c..7607d85 100644
--- a/src/com/android/launcher3/widget/WidgetHostViewLoader.java
+++ b/src/com/android/launcher3/widget/WidgetHostViewLoader.java
@@ -10,13 +10,14 @@
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 {
@@ -132,7 +133,7 @@
public static Bundle getDefaultOptionsForWidget(Launcher launcher, PendingAddWidgetInfo info) {
Bundle options = null;
Rect rect = new Rect();
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ if (Utilities.ATLEAST_JB_MR1) {
AppWidgetResizeFrame.getWidgetSizeRanges(launcher, info.spanX, info.spanY, rect);
Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(launcher,
info.componentName, null);
diff --git a/src/com/android/launcher3/widget/WidgetsContainerView.java b/src/com/android/launcher3/widget/WidgetsContainerView.java
index 0f43a34..e6059d5 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.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.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 @@
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");
@@ -319,7 +319,6 @@
CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen);
ItemInfo itemInfo = d.dragInfo;
if (layout != null) {
- layout.calculateSpans(itemInfo);
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 a54626a..ac9d62e 100644
--- a/src/com/android/launcher3/widget/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/WidgetsListAdapter.java
@@ -27,7 +27,6 @@
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 @@
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) {
@@ -169,7 +165,7 @@
// if the end padding is 0, then container view (horizontal scroll view) doesn't respect
// the end of the linear layout width + the start padding and doesn't allow scrolling.
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ if (Utilities.ATLEAST_JB_MR1) {
cellList.setPaddingRelative(mIndent, 0, 1, 0);
} else {
cellList.setPadding(mIndent, 0, 1, 0);
@@ -207,12 +203,4 @@
}
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/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
index 61e63cd..e586dc2 100644
--- a/src/com/android/launcher3/widget/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
@@ -102,7 +102,7 @@
getCurScrollState(mScrollPosState);
float pos = rowCount * touchFraction;
- int availableScrollHeight = getAvailableScrollHeight(rowCount, mScrollPosState.rowHeight, 0);
+ int availableScrollHeight = getAvailableScrollHeight(rowCount, mScrollPosState.rowHeight);
LinearLayoutManager layoutManager = ((LinearLayoutManager) getLayoutManager());
layoutManager.scrollToPositionWithOffset(0, (int) -(availableScrollHeight * touchFraction));
@@ -115,29 +115,29 @@
* Updates the bounds for the scrollbar.
*/
@Override
- public void onUpdateScrollbar() {
+ public void onUpdateScrollbar(int dy) {
int rowCount = mWidgets.getPackageSize();
// Skip early if, there are no items.
if (rowCount == 0) {
- mScrollbar.setScrollbarThumbOffset(-1, -1);
+ mScrollbar.setThumbOffset(-1, -1);
return;
}
// Skip early if, there no child laid out in the container.
getCurScrollState(mScrollPosState);
if (mScrollPosState.rowIndex < 0) {
- mScrollbar.setScrollbarThumbOffset(-1, -1);
+ mScrollbar.setThumbOffset(-1, -1);
return;
}
- synchronizeScrollBarThumbOffsetToViewScroll(mScrollPosState, rowCount, 0);
+ synchronizeScrollBarThumbOffsetToViewScroll(mScrollPosState, rowCount);
}
/**
* Returns the current scroll state.
*/
- private void getCurScrollState(ScrollPositionState stateOut) {
+ protected void getCurScrollState(ScrollPositionState stateOut) {
stateOut.rowIndex = -1;
stateOut.rowTopOffset = -1;
stateOut.rowHeight = -1;
diff --git a/src/com/android/launcher3/widget/WidgetsRowViewHolder.java b/src/com/android/launcher3/widget/WidgetsRowViewHolder.java
index 249559a..19bc868 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 {