summaryrefslogtreecommitdiffstats
path: root/src/com
diff options
context:
space:
mode:
Diffstat (limited to 'src/com')
-rw-r--r--src/com/android/gallery3d/common/BitmapUtils.java260
-rw-r--r--src/com/android/gallery3d/common/Utils.java340
-rw-r--r--src/com/android/gallery3d/exif/ByteBufferInputStream.java48
-rw-r--r--src/com/android/gallery3d/exif/CountedDataInputStream.java136
-rw-r--r--src/com/android/gallery3d/exif/ExifData.java348
-rw-r--r--src/com/android/gallery3d/exif/ExifInterface.java2407
-rw-r--r--src/com/android/gallery3d/exif/ExifModifier.java196
-rw-r--r--src/com/android/gallery3d/exif/ExifOutputStream.java518
-rw-r--r--src/com/android/gallery3d/exif/ExifParser.java916
-rw-r--r--src/com/android/gallery3d/exif/ExifReader.java92
-rw-r--r--src/com/android/gallery3d/exif/ExifTag.java1008
-rw-r--r--src/com/android/gallery3d/exif/IfdData.java152
-rw-r--r--src/com/android/gallery3d/exif/IfdId.java31
-rw-r--r--src/com/android/gallery3d/exif/JpegHeader.java39
-rw-r--r--src/com/android/gallery3d/exif/OrderedDataOutputStream.java56
-rw-r--r--src/com/android/gallery3d/exif/Rational.java88
-rw-r--r--src/com/android/gallery3d/glrenderer/BasicTexture.java212
-rw-r--r--src/com/android/gallery3d/glrenderer/BitmapTexture.java54
-rw-r--r--src/com/android/gallery3d/glrenderer/GLCanvas.java217
-rw-r--r--src/com/android/gallery3d/glrenderer/GLES20Canvas.java1009
-rw-r--r--src/com/android/gallery3d/glrenderer/GLES20IdImpl.java42
-rw-r--r--src/com/android/gallery3d/glrenderer/GLId.java33
-rw-r--r--src/com/android/gallery3d/glrenderer/RawTexture.java73
-rw-r--r--src/com/android/gallery3d/glrenderer/Texture.java44
-rw-r--r--src/com/android/gallery3d/glrenderer/UploadedTexture.java298
-rw-r--r--src/com/android/gallery3d/util/IntArray.java60
-rw-r--r--src/com/android/launcher/home/Home.java201
-rw-r--r--src/com/android/launcher3/AddAdapter.java2
-rw-r--r--src/com/android/launcher3/Alarm.java4
-rw-r--r--src/com/android/launcher3/AllAppsList.java11
-rw-r--r--src/com/android/launcher3/AppInfo.java6
-rw-r--r--src/com/android/launcher3/AppWidgetResizeFrame.java2
-rw-r--r--src/com/android/launcher3/AppsCustomizeLayout.java12
-rw-r--r--src/com/android/launcher3/AppsCustomizePagedView.java174
-rw-r--r--src/com/android/launcher3/BubbleTextView.java9
-rw-r--r--src/com/android/launcher3/BuildInfo.java32
-rw-r--r--src/com/android/launcher3/ButtonDropTarget.java2
-rw-r--r--src/com/android/launcher3/CellLayout.java177
-rw-r--r--src/com/android/launcher3/CheckableFrameLayout.java63
-rw-r--r--src/com/android/launcher3/Cling.java192
-rw-r--r--src/com/android/launcher3/CropView.java321
-rw-r--r--src/com/android/launcher3/DeferredHandler.java1
-rw-r--r--src/com/android/launcher3/DeleteDropTarget.java42
-rw-r--r--src/com/android/launcher3/DeviceProfile.java886
-rw-r--r--src/com/android/launcher3/DragController.java22
-rw-r--r--src/com/android/launcher3/DragLayer.java109
-rw-r--r--src/com/android/launcher3/DragSource.java16
-rw-r--r--src/com/android/launcher3/DragView.java15
-rw-r--r--src/com/android/launcher3/DrawableStateProxyView.java2
-rw-r--r--src/com/android/launcher3/DynamicGrid.java512
-rw-r--r--src/com/android/launcher3/DynamicGridSizeFragment.java331
-rw-r--r--src/com/android/launcher3/FastBitmapDrawable.java30
-rw-r--r--src/com/android/launcher3/FirstFrameAnimatorHelper.java9
-rw-r--r--src/com/android/launcher3/FocusHelper.java2
-rw-r--r--src/com/android/launcher3/Folder.java342
-rw-r--r--src/com/android/launcher3/FolderIcon.java77
-rw-r--r--src/com/android/launcher3/FolderInfo.java8
-rw-r--r--src/com/android/launcher3/GelIntegrationHelper.java102
-rw-r--r--src/com/android/launcher3/HiddenFolderFragment.java343
-rw-r--r--src/com/android/launcher3/Hotseat.java4
-rw-r--r--src/com/android/launcher3/IconCache.java245
-rw-r--r--src/com/android/launcher3/IconPackHelper.java517
-rw-r--r--src/com/android/launcher3/IconPickerActivity.java152
-rw-r--r--src/com/android/launcher3/InstallShortcutReceiver.java76
-rw-r--r--src/com/android/launcher3/InstallWidgetReceiver.java4
-rw-r--r--src/com/android/launcher3/ItemInfo.java6
-rw-r--r--src/com/android/launcher3/Launcher.java1477
-rw-r--r--src/com/android/launcher3/LauncherAnimUtils.java10
-rw-r--r--src/com/android/launcher3/LauncherAppState.java72
-rw-r--r--src/com/android/launcher3/LauncherAppWidgetHostView.java14
-rw-r--r--src/com/android/launcher3/LauncherBackupAgentHelper.java30
-rw-r--r--src/com/android/launcher3/LauncherBackupHelper.java323
-rw-r--r--src/com/android/launcher3/LauncherClings.java451
-rw-r--r--src/com/android/launcher3/LauncherModel.java741
-rw-r--r--src/com/android/launcher3/LauncherPreferencesBackupHelper.java44
-rw-r--r--src/com/android/launcher3/LauncherProvider.java510
-rw-r--r--src/com/android/launcher3/LauncherScroller.java559
-rw-r--r--src/com/android/launcher3/LauncherSettings.java19
-rw-r--r--src/com/android/launcher3/LauncherWallpaperPickerActivity.java (renamed from src/com/android/gallery3d/exif/ExifInvalidFormatException.java)19
-rw-r--r--src/com/android/launcher3/Lists.java64
-rw-r--r--src/com/android/launcher3/LiveWallpaperListAdapter.java203
-rw-r--r--src/com/android/launcher3/LockWallpaperPickerActivity.java1600
-rw-r--r--src/com/android/launcher3/OnAlarmListener.java5
-rw-r--r--src/com/android/launcher3/OverviewSettingsPanel.java233
-rw-r--r--src/com/android/launcher3/PageIndicator.java10
-rw-r--r--src/com/android/launcher3/PageIndicatorMarker.java8
-rw-r--r--src/com/android/launcher3/PagedView.java303
-rw-r--r--src/com/android/launcher3/PagedViewIcon.java12
-rw-r--r--src/com/android/launcher3/PagedViewIconCache.java10
-rw-r--r--src/com/android/launcher3/PagedViewWidget.java6
-rw-r--r--src/com/android/launcher3/PreloadReceiver.java9
-rw-r--r--src/com/android/launcher3/RampUpScroller.java120
-rw-r--r--src/com/android/launcher3/SavedWallpaperImages.java244
-rw-r--r--src/com/android/launcher3/ScrimView.java3
-rw-r--r--src/com/android/launcher3/SearchDropTargetBar.java9
-rw-r--r--src/com/android/launcher3/ShortcutInfo.java32
-rw-r--r--src/com/android/launcher3/SlidingUpPanelLayout.java1315
-rw-r--r--src/com/android/launcher3/SmoothPagedView.java5
-rw-r--r--src/com/android/launcher3/ThemeChangedReceiver.java79
-rw-r--r--src/com/android/launcher3/ThirdPartyWallpaperPickerListAdapter.java139
-rw-r--r--src/com/android/launcher3/TransitionEffectsFragment.java220
-rw-r--r--src/com/android/launcher3/Utilities.java83
-rw-r--r--src/com/android/launcher3/WallpaperChangedReceiver.java (renamed from src/com/android/gallery3d/glrenderer/GLPaint.java)32
-rw-r--r--src/com/android/launcher3/WallpaperCropActivity.java793
-rw-r--r--src/com/android/launcher3/WallpaperPickerActivity.java884
-rw-r--r--src/com/android/launcher3/WallpaperRootView.java39
-rw-r--r--src/com/android/launcher3/WidgetPreviewLoader.java87
-rw-r--r--src/com/android/launcher3/Workspace.java751
-rw-r--r--src/com/android/launcher3/list/AutoScrollListView.java117
-rw-r--r--src/com/android/launcher3/list/CompositeCursorAdapter.java532
-rw-r--r--src/com/android/launcher3/list/PinnedHeaderListAdapter.java172
-rw-r--r--src/com/android/launcher3/list/PinnedHeaderListView.java565
-rw-r--r--src/com/android/launcher3/list/SettingsPinnedHeaderAdapter.java373
-rw-r--r--src/com/android/launcher3/settings/FontStylePreference.java142
-rw-r--r--src/com/android/launcher3/settings/HiddenAppsActivity.java302
-rw-r--r--src/com/android/launcher3/settings/SettingsActivity.java274
-rw-r--r--src/com/android/launcher3/settings/SettingsProvider.java15
-rw-r--r--src/com/android/photos/BitmapRegionTileSource.java288
-rw-r--r--src/com/android/photos/views/BlockingGLTextureView.java438
-rw-r--r--src/com/android/photos/views/TiledImageRenderer.java825
-rw-r--r--src/com/android/photos/views/TiledImageView.java386
121 files changed, 10842 insertions, 18892 deletions
diff --git a/src/com/android/gallery3d/common/BitmapUtils.java b/src/com/android/gallery3d/common/BitmapUtils.java
deleted file mode 100644
index a671ed2b9..000000000
--- a/src/com/android/gallery3d/common/BitmapUtils.java
+++ /dev/null
@@ -1,260 +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.common;
-
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.CompressFormat;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.os.Build;
-import android.util.FloatMath;
-import android.util.Log;
-
-import java.io.ByteArrayOutputStream;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-public class BitmapUtils {
- private static final String TAG = "BitmapUtils";
- private static final int DEFAULT_JPEG_QUALITY = 90;
- public static final int UNCONSTRAINED = -1;
-
- private BitmapUtils(){}
-
- /*
- * Compute the sample size as a function of minSideLength
- * and maxNumOfPixels.
- * minSideLength is used to specify that minimal width or height of a
- * bitmap.
- * maxNumOfPixels is used to specify the maximal size in pixels that is
- * tolerable in terms of memory usage.
- *
- * The function returns a sample size based on the constraints.
- * Both size and minSideLength can be passed in as UNCONSTRAINED,
- * which indicates no care of the corresponding constraint.
- * The functions prefers returning a sample size that
- * generates a smaller bitmap, unless minSideLength = UNCONSTRAINED.
- *
- * Also, the function rounds up the sample size to a power of 2 or multiple
- * of 8 because BitmapFactory only honors sample size this way.
- * For example, BitmapFactory downsamples an image by 2 even though the
- * request is 3. So we round up the sample size to avoid OOM.
- */
- public static int computeSampleSize(int width, int height,
- int minSideLength, int maxNumOfPixels) {
- int initialSize = computeInitialSampleSize(
- width, height, minSideLength, maxNumOfPixels);
-
- return initialSize <= 8
- ? Utils.nextPowerOf2(initialSize)
- : (initialSize + 7) / 8 * 8;
- }
-
- private static int computeInitialSampleSize(int w, int h,
- int minSideLength, int maxNumOfPixels) {
- if (maxNumOfPixels == UNCONSTRAINED
- && minSideLength == UNCONSTRAINED) return 1;
-
- int lowerBound = (maxNumOfPixels == UNCONSTRAINED) ? 1 :
- (int) FloatMath.ceil(FloatMath.sqrt((float) (w * h) / maxNumOfPixels));
-
- if (minSideLength == UNCONSTRAINED) {
- return lowerBound;
- } else {
- int sampleSize = Math.min(w / minSideLength, h / minSideLength);
- return Math.max(sampleSize, lowerBound);
- }
- }
-
- // This computes a sample size which makes the longer side at least
- // minSideLength long. If that's not possible, return 1.
- public static int computeSampleSizeLarger(int w, int h,
- int minSideLength) {
- int initialSize = Math.max(w / minSideLength, h / minSideLength);
- if (initialSize <= 1) return 1;
-
- return initialSize <= 8
- ? Utils.prevPowerOf2(initialSize)
- : initialSize / 8 * 8;
- }
-
- // Find the min x that 1 / x >= scale
- public static int computeSampleSizeLarger(float scale) {
- int initialSize = (int) FloatMath.floor(1f / scale);
- if (initialSize <= 1) return 1;
-
- return initialSize <= 8
- ? Utils.prevPowerOf2(initialSize)
- : initialSize / 8 * 8;
- }
-
- // Find the max x that 1 / x <= scale.
- public static int computeSampleSize(float scale) {
- Utils.assertTrue(scale > 0);
- int initialSize = Math.max(1, (int) FloatMath.ceil(1 / scale));
- return initialSize <= 8
- ? Utils.nextPowerOf2(initialSize)
- : (initialSize + 7) / 8 * 8;
- }
-
- public static Bitmap resizeBitmapByScale(
- Bitmap bitmap, float scale, boolean recycle) {
- int width = Math.round(bitmap.getWidth() * scale);
- int height = Math.round(bitmap.getHeight() * scale);
- if (width == bitmap.getWidth()
- && height == bitmap.getHeight()) return bitmap;
- Bitmap target = Bitmap.createBitmap(width, height, getConfig(bitmap));
- Canvas canvas = new Canvas(target);
- canvas.scale(scale, scale);
- Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG);
- canvas.drawBitmap(bitmap, 0, 0, paint);
- if (recycle) bitmap.recycle();
- return target;
- }
-
- private static Bitmap.Config getConfig(Bitmap bitmap) {
- Bitmap.Config config = bitmap.getConfig();
- if (config == null) {
- config = Bitmap.Config.ARGB_8888;
- }
- return config;
- }
-
- public static Bitmap resizeDownBySideLength(
- Bitmap bitmap, int maxLength, boolean recycle) {
- int srcWidth = bitmap.getWidth();
- int srcHeight = bitmap.getHeight();
- float scale = Math.min(
- (float) maxLength / srcWidth, (float) maxLength / srcHeight);
- if (scale >= 1.0f) return bitmap;
- return resizeBitmapByScale(bitmap, scale, recycle);
- }
-
- public static Bitmap resizeAndCropCenter(Bitmap bitmap, int size, boolean recycle) {
- int w = bitmap.getWidth();
- int h = bitmap.getHeight();
- if (w == size && h == size) return bitmap;
-
- // scale the image so that the shorter side equals to the target;
- // the longer side will be center-cropped.
- float scale = (float) size / Math.min(w, h);
-
- Bitmap target = Bitmap.createBitmap(size, size, getConfig(bitmap));
- int width = Math.round(scale * bitmap.getWidth());
- int height = Math.round(scale * bitmap.getHeight());
- Canvas canvas = new Canvas(target);
- canvas.translate((size - width) / 2f, (size - height) / 2f);
- canvas.scale(scale, scale);
- Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG);
- canvas.drawBitmap(bitmap, 0, 0, paint);
- if (recycle) bitmap.recycle();
- return target;
- }
-
- public static void recycleSilently(Bitmap bitmap) {
- if (bitmap == null) return;
- try {
- bitmap.recycle();
- } catch (Throwable t) {
- Log.w(TAG, "unable recycle bitmap", t);
- }
- }
-
- public static Bitmap rotateBitmap(Bitmap source, int rotation, boolean recycle) {
- if (rotation == 0) return source;
- int w = source.getWidth();
- int h = source.getHeight();
- Matrix m = new Matrix();
- m.postRotate(rotation);
- Bitmap bitmap = Bitmap.createBitmap(source, 0, 0, w, h, m, true);
- if (recycle) source.recycle();
- return bitmap;
- }
-
- public static Bitmap createVideoThumbnail(String filePath) {
- // MediaMetadataRetriever is available on API Level 8
- // but is hidden until API Level 10
- Class<?> clazz = null;
- Object instance = null;
- try {
- clazz = Class.forName("android.media.MediaMetadataRetriever");
- instance = clazz.newInstance();
-
- Method method = clazz.getMethod("setDataSource", String.class);
- method.invoke(instance, filePath);
-
- // The method name changes between API Level 9 and 10.
- if (Build.VERSION.SDK_INT <= 9) {
- return (Bitmap) clazz.getMethod("captureFrame").invoke(instance);
- } else {
- byte[] data = (byte[]) clazz.getMethod("getEmbeddedPicture").invoke(instance);
- if (data != null) {
- Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
- if (bitmap != null) return bitmap;
- }
- return (Bitmap) clazz.getMethod("getFrameAtTime").invoke(instance);
- }
- } catch (IllegalArgumentException ex) {
- // Assume this is a corrupt video file
- } catch (RuntimeException ex) {
- // Assume this is a corrupt video file.
- } catch (InstantiationException e) {
- Log.e(TAG, "createVideoThumbnail", e);
- } catch (InvocationTargetException e) {
- Log.e(TAG, "createVideoThumbnail", e);
- } catch (ClassNotFoundException e) {
- Log.e(TAG, "createVideoThumbnail", e);
- } catch (NoSuchMethodException e) {
- Log.e(TAG, "createVideoThumbnail", e);
- } catch (IllegalAccessException e) {
- Log.e(TAG, "createVideoThumbnail", e);
- } finally {
- try {
- if (instance != null) {
- clazz.getMethod("release").invoke(instance);
- }
- } catch (Exception ignored) {
- }
- }
- return null;
- }
-
- public static byte[] compressToBytes(Bitmap bitmap) {
- return compressToBytes(bitmap, DEFAULT_JPEG_QUALITY);
- }
-
- public static byte[] compressToBytes(Bitmap bitmap, int quality) {
- ByteArrayOutputStream baos = new ByteArrayOutputStream(65536);
- bitmap.compress(CompressFormat.JPEG, quality, baos);
- return baos.toByteArray();
- }
-
- public static boolean isSupportedByRegionDecoder(String mimeType) {
- if (mimeType == null) return false;
- mimeType = mimeType.toLowerCase();
- return mimeType.startsWith("image/") &&
- (!mimeType.equals("image/gif") && !mimeType.endsWith("bmp"));
- }
-
- public static boolean isRotationSupported(String mimeType) {
- if (mimeType == null) return false;
- mimeType = mimeType.toLowerCase();
- return mimeType.equals("image/jpeg");
- }
-}
diff --git a/src/com/android/gallery3d/common/Utils.java b/src/com/android/gallery3d/common/Utils.java
deleted file mode 100644
index 614a081c8..000000000
--- a/src/com/android/gallery3d/common/Utils.java
+++ /dev/null
@@ -1,340 +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.common;
-
-import android.content.Context;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.database.Cursor;
-import android.os.Build;
-import android.os.ParcelFileDescriptor;
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.io.Closeable;
-import java.io.IOException;
-import java.io.InterruptedIOException;
-
-public class Utils {
- private static final String TAG = "Utils";
- private static final String DEBUG_TAG = "GalleryDebug";
-
- private static final long POLY64REV = 0x95AC9329AC4BC9B5L;
- private static final long INITIALCRC = 0xFFFFFFFFFFFFFFFFL;
-
- private static long[] sCrcTable = new long[256];
-
- private static final boolean IS_DEBUG_BUILD =
- Build.TYPE.equals("eng") || Build.TYPE.equals("userdebug");
-
- private static final String MASK_STRING = "********************************";
-
- // Throws AssertionError if the input is false.
- public static void assertTrue(boolean cond) {
- if (!cond) {
- throw new AssertionError();
- }
- }
-
- // Throws AssertionError with the message. We had a method having the form
- // assertTrue(boolean cond, String message, Object ... args);
- // However a call to that method will cause memory allocation even if the
- // condition is false (due to autoboxing generated by "Object ... args"),
- // so we don't use that anymore.
- public static void fail(String message, Object ... args) {
- throw new AssertionError(
- args.length == 0 ? message : String.format(message, args));
- }
-
- // Throws NullPointerException if the input is null.
- public static <T> T checkNotNull(T object) {
- if (object == null) throw new NullPointerException();
- return object;
- }
-
- // Returns true if two input Object are both null or equal
- // to each other.
- public static boolean equals(Object a, Object b) {
- return (a == b) || (a == null ? false : a.equals(b));
- }
-
- // Returns the next power of two.
- // Returns the input if it is already power of 2.
- // Throws IllegalArgumentException if the input is <= 0 or
- // the answer overflows.
- public static int nextPowerOf2(int n) {
- if (n <= 0 || n > (1 << 30)) throw new IllegalArgumentException("n is invalid: " + n);
- n -= 1;
- n |= n >> 16;
- n |= n >> 8;
- n |= n >> 4;
- n |= n >> 2;
- n |= n >> 1;
- return n + 1;
- }
-
- // Returns the previous power of two.
- // Returns the input if it is already power of 2.
- // Throws IllegalArgumentException if the input is <= 0
- public static int prevPowerOf2(int n) {
- if (n <= 0) throw new IllegalArgumentException();
- return Integer.highestOneBit(n);
- }
-
- // Returns the input value x clamped to the range [min, max].
- public static int clamp(int x, int min, int max) {
- if (x > max) return max;
- if (x < min) return min;
- return x;
- }
-
- // Returns the input value x clamped to the range [min, max].
- public static float clamp(float x, float min, float max) {
- if (x > max) return max;
- if (x < min) return min;
- return x;
- }
-
- // Returns the input value x clamped to the range [min, max].
- public static long clamp(long x, long min, long max) {
- if (x > max) return max;
- if (x < min) return min;
- return x;
- }
-
- public static boolean isOpaque(int color) {
- return color >>> 24 == 0xFF;
- }
-
- public static void swap(int[] array, int i, int j) {
- int temp = array[i];
- array[i] = array[j];
- array[j] = temp;
- }
-
- /**
- * A function thats returns a 64-bit crc for string
- *
- * @param in input string
- * @return a 64-bit crc value
- */
- public static final long crc64Long(String in) {
- if (in == null || in.length() == 0) {
- return 0;
- }
- return crc64Long(getBytes(in));
- }
-
- static {
- // http://bioinf.cs.ucl.ac.uk/downloads/crc64/crc64.c
- long part;
- for (int i = 0; i < 256; i++) {
- part = i;
- for (int j = 0; j < 8; j++) {
- long x = ((int) part & 1) != 0 ? POLY64REV : 0;
- part = (part >> 1) ^ x;
- }
- sCrcTable[i] = part;
- }
- }
-
- public static final long crc64Long(byte[] buffer) {
- long crc = INITIALCRC;
- for (int k = 0, n = buffer.length; k < n; ++k) {
- crc = sCrcTable[(((int) crc) ^ buffer[k]) & 0xff] ^ (crc >> 8);
- }
- return crc;
- }
-
- public static byte[] getBytes(String in) {
- byte[] result = new byte[in.length() * 2];
- int output = 0;
- for (char ch : in.toCharArray()) {
- result[output++] = (byte) (ch & 0xFF);
- result[output++] = (byte) (ch >> 8);
- }
- return result;
- }
-
- public static void closeSilently(Closeable c) {
- if (c == null) return;
- try {
- c.close();
- } catch (IOException t) {
- Log.w(TAG, "close fail ", t);
- }
- }
-
- public static int compare(long a, long b) {
- return a < b ? -1 : a == b ? 0 : 1;
- }
-
- public static int ceilLog2(float value) {
- int i;
- for (i = 0; i < 31; i++) {
- if ((1 << i) >= value) break;
- }
- return i;
- }
-
- public static int floorLog2(float value) {
- int i;
- for (i = 0; i < 31; i++) {
- if ((1 << i) > value) break;
- }
- return i - 1;
- }
-
- public static void closeSilently(ParcelFileDescriptor fd) {
- try {
- if (fd != null) fd.close();
- } catch (Throwable t) {
- Log.w(TAG, "fail to close", t);
- }
- }
-
- public static void closeSilently(Cursor cursor) {
- try {
- if (cursor != null) cursor.close();
- } catch (Throwable t) {
- Log.w(TAG, "fail to close", t);
- }
- }
-
- public static float interpolateAngle(
- float source, float target, float progress) {
- // interpolate the angle from source to target
- // We make the difference in the range of [-179, 180], this is the
- // shortest path to change source to target.
- float diff = target - source;
- if (diff < 0) diff += 360f;
- if (diff > 180) diff -= 360f;
-
- float result = source + diff * progress;
- return result < 0 ? result + 360f : result;
- }
-
- public static float interpolateScale(
- float source, float target, float progress) {
- return source + progress * (target - source);
- }
-
- public static String ensureNotNull(String value) {
- return value == null ? "" : value;
- }
-
- public static float parseFloatSafely(String content, float defaultValue) {
- if (content == null) return defaultValue;
- try {
- return Float.parseFloat(content);
- } catch (NumberFormatException e) {
- return defaultValue;
- }
- }
-
- public static int parseIntSafely(String content, int defaultValue) {
- if (content == null) return defaultValue;
- try {
- return Integer.parseInt(content);
- } catch (NumberFormatException e) {
- return defaultValue;
- }
- }
-
- public static boolean isNullOrEmpty(String exifMake) {
- return TextUtils.isEmpty(exifMake);
- }
-
- public static void waitWithoutInterrupt(Object object) {
- try {
- object.wait();
- } catch (InterruptedException e) {
- Log.w(TAG, "unexpected interrupt: " + object);
- }
- }
-
- public static boolean handleInterrruptedException(Throwable e) {
- // A helper to deal with the interrupt exception
- // If an interrupt detected, we will setup the bit again.
- if (e instanceof InterruptedIOException
- || e instanceof InterruptedException) {
- Thread.currentThread().interrupt();
- return true;
- }
- return false;
- }
-
- /**
- * @return String with special XML characters escaped.
- */
- public static String escapeXml(String s) {
- StringBuilder sb = new StringBuilder();
- for (int i = 0, len = s.length(); i < len; ++i) {
- char c = s.charAt(i);
- switch (c) {
- case '<': sb.append("&lt;"); break;
- case '>': sb.append("&gt;"); break;
- case '\"': sb.append("&quot;"); break;
- case '\'': sb.append("&#039;"); break;
- case '&': sb.append("&amp;"); break;
- default: sb.append(c);
- }
- }
- return sb.toString();
- }
-
- public static String getUserAgent(Context context) {
- PackageInfo packageInfo;
- try {
- packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
- } catch (NameNotFoundException e) {
- throw new IllegalStateException("getPackageInfo failed");
- }
- return String.format("%s/%s; %s/%s/%s/%s; %s/%s/%s",
- packageInfo.packageName,
- packageInfo.versionName,
- Build.BRAND,
- Build.DEVICE,
- Build.MODEL,
- Build.ID,
- Build.VERSION.SDK_INT,
- Build.VERSION.RELEASE,
- Build.VERSION.INCREMENTAL);
- }
-
- public static String[] copyOf(String[] source, int newSize) {
- String[] result = new String[newSize];
- newSize = Math.min(source.length, newSize);
- System.arraycopy(source, 0, result, 0, newSize);
- return result;
- }
-
- // Mask information for debugging only. It returns <code>info.toString()</code> directly
- // for debugging build (i.e., 'eng' and 'userdebug') and returns a mask ("****")
- // in release build to protect the information (e.g. for privacy issue).
- public static String maskDebugInfo(Object info) {
- if (info == null) return null;
- String s = info.toString();
- int length = Math.min(s.length(), MASK_STRING.length());
- return IS_DEBUG_BUILD ? s : MASK_STRING.substring(0, length);
- }
-
- // This method should be ONLY used for debugging.
- public static void debug(String message, Object ... args) {
- Log.v(DEBUG_TAG, String.format(message, args));
- }
-}
diff --git a/src/com/android/gallery3d/exif/ByteBufferInputStream.java b/src/com/android/gallery3d/exif/ByteBufferInputStream.java
deleted file mode 100644
index 7fb9f22cc..000000000
--- a/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/src/com/android/gallery3d/exif/CountedDataInputStream.java b/src/com/android/gallery3d/exif/CountedDataInputStream.java
deleted file mode 100644
index dfd4a1a10..000000000
--- a/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/src/com/android/gallery3d/exif/ExifData.java b/src/com/android/gallery3d/exif/ExifData.java
deleted file mode 100644
index 8422382bb..000000000
--- a/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/src/com/android/gallery3d/exif/ExifInterface.java b/src/com/android/gallery3d/exif/ExifInterface.java
deleted file mode 100644
index a1cf0fc85..000000000
--- a/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 new Long(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 new Integer(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 new Byte(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/src/com/android/gallery3d/exif/ExifModifier.java b/src/com/android/gallery3d/exif/ExifModifier.java
deleted file mode 100644
index f00362b6b..000000000
--- a/src/com/android/gallery3d/exif/ExifModifier.java
+++ /dev/null
@@ -1,196 +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.Closeable;
-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/src/com/android/gallery3d/exif/ExifOutputStream.java b/src/com/android/gallery3d/exif/ExifOutputStream.java
deleted file mode 100644
index 7ca05f2e0..000000000
--- a/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/src/com/android/gallery3d/exif/ExifParser.java b/src/com/android/gallery3d/exif/ExifParser.java
deleted file mode 100644
index 5467d423d..000000000
--- a/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/src/com/android/gallery3d/exif/ExifReader.java b/src/com/android/gallery3d/exif/ExifReader.java
deleted file mode 100644
index 68e972fb7..000000000
--- a/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/src/com/android/gallery3d/exif/ExifTag.java b/src/com/android/gallery3d/exif/ExifTag.java
deleted file mode 100644
index b8b387201..000000000
--- a/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/src/com/android/gallery3d/exif/IfdData.java b/src/com/android/gallery3d/exif/IfdData.java
deleted file mode 100644
index 093944aec..000000000
--- a/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/src/com/android/gallery3d/exif/IfdId.java b/src/com/android/gallery3d/exif/IfdId.java
deleted file mode 100644
index 7842edbd4..000000000
--- a/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/src/com/android/gallery3d/exif/JpegHeader.java b/src/com/android/gallery3d/exif/JpegHeader.java
deleted file mode 100644
index e3e787eff..000000000
--- a/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/src/com/android/gallery3d/exif/OrderedDataOutputStream.java b/src/com/android/gallery3d/exif/OrderedDataOutputStream.java
deleted file mode 100644
index 428e6b9fc..000000000
--- a/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/src/com/android/gallery3d/exif/Rational.java b/src/com/android/gallery3d/exif/Rational.java
deleted file mode 100644
index 591d63faf..000000000
--- a/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/src/com/android/gallery3d/glrenderer/BasicTexture.java b/src/com/android/gallery3d/glrenderer/BasicTexture.java
deleted file mode 100644
index 2e77b903f..000000000
--- a/src/com/android/gallery3d/glrenderer/BasicTexture.java
+++ /dev/null
@@ -1,212 +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 com.android.gallery3d.common.Utils;
-
-import java.util.WeakHashMap;
-
-// BasicTexture is a Texture corresponds to a real GL texture.
-// The state of a BasicTexture indicates whether its data is loaded to GL memory.
-// If a BasicTexture is loaded into GL memory, it has a GL texture id.
-public abstract class BasicTexture implements Texture {
-
- @SuppressWarnings("unused")
- private static final String TAG = "BasicTexture";
- protected static final int UNSPECIFIED = -1;
-
- protected static final int STATE_UNLOADED = 0;
- protected static final int STATE_LOADED = 1;
- protected static final int STATE_ERROR = -1;
-
- // Log a warning if a texture is larger along a dimension
- private static final int MAX_TEXTURE_SIZE = 4096;
-
- protected int mId = -1;
- protected int mState;
-
- protected int mWidth = UNSPECIFIED;
- protected int mHeight = UNSPECIFIED;
-
- protected int mTextureWidth;
- protected int mTextureHeight;
-
- private boolean mHasBorder;
-
- protected GLCanvas mCanvasRef = null;
- private static WeakHashMap<BasicTexture, Object> sAllTextures
- = new WeakHashMap<BasicTexture, Object>();
- private static ThreadLocal sInFinalizer = new ThreadLocal();
-
- protected BasicTexture(GLCanvas canvas, int id, int state) {
- setAssociatedCanvas(canvas);
- mId = id;
- mState = state;
- synchronized (sAllTextures) {
- sAllTextures.put(this, null);
- }
- }
-
- protected BasicTexture() {
- this(null, 0, STATE_UNLOADED);
- }
-
- protected void setAssociatedCanvas(GLCanvas canvas) {
- mCanvasRef = canvas;
- }
-
- /**
- * Sets the content size of this texture. In OpenGL, the actual texture
- * size must be of power of 2, the size of the content may be smaller.
- */
- public void setSize(int width, int height) {
- mWidth = width;
- mHeight = height;
- mTextureWidth = width > 0 ? Utils.nextPowerOf2(width) : 0;
- mTextureHeight = height > 0 ? Utils.nextPowerOf2(height) : 0;
- if (mTextureWidth > MAX_TEXTURE_SIZE || mTextureHeight > MAX_TEXTURE_SIZE) {
- Log.w(TAG, String.format("texture is too large: %d x %d",
- mTextureWidth, mTextureHeight), new Exception());
- }
- }
-
- public boolean isFlippedVertically() {
- return false;
- }
-
- public int getId() {
- return mId;
- }
-
- @Override
- public int getWidth() {
- return mWidth;
- }
-
- @Override
- public int getHeight() {
- return mHeight;
- }
-
- // Returns the width rounded to the next power of 2.
- public int getTextureWidth() {
- return mTextureWidth;
- }
-
- // Returns the height rounded to the next power of 2.
- public int getTextureHeight() {
- 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());
- }
-
- @Override
- public void draw(GLCanvas canvas, int x, int y, int w, int h) {
- canvas.drawTexture(this, x, y, w, h);
- }
-
- // onBind is called before GLCanvas binds this texture.
- // It should make sure the data is uploaded to GL memory.
- abstract protected boolean onBind(GLCanvas canvas);
-
- // Returns the GL texture target for this texture (e.g. GL_TEXTURE_2D).
- abstract protected int getTarget();
-
- public boolean isLoaded() {
- return mState == STATE_LOADED;
- }
-
- // recycle() is called when the texture will never be used again,
- // so it can free all resources.
- public void recycle() {
- freeResource();
- }
-
- // yield() is called when the texture will not be used temporarily,
- // so it can free some resources.
- // The default implementation unloads the texture from GL memory, so
- // the subclass should make sure it can reload the texture to GL memory
- // later, or it will have to override this method.
- public void yield() {
- freeResource();
- }
-
- private void freeResource() {
- GLCanvas canvas = mCanvasRef;
- if (canvas != null && mId != -1) {
- canvas.unloadTexture(this);
- mId = -1; // Don't free it again.
- }
- mState = STATE_UNLOADED;
- setAssociatedCanvas(null);
- }
-
- @Override
- protected void finalize() {
- sInFinalizer.set(BasicTexture.class);
- recycle();
- 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()) {
- t.yield();
- }
- }
- }
-
- public static void invalidateAllTextures() {
- synchronized (sAllTextures) {
- for (BasicTexture t : sAllTextures.keySet()) {
- t.mState = STATE_UNLOADED;
- t.setAssociatedCanvas(null);
- }
- }
- }
-}
diff --git a/src/com/android/gallery3d/glrenderer/BitmapTexture.java b/src/com/android/gallery3d/glrenderer/BitmapTexture.java
deleted file mode 100644
index 100b0b3b9..000000000
--- a/src/com/android/gallery3d/glrenderer/BitmapTexture.java
+++ /dev/null
@@ -1,54 +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.graphics.Bitmap;
-
-import junit.framework.Assert;
-
-// BitmapTexture is a texture whose content is specified by a fixed Bitmap.
-//
-// The texture does not own the Bitmap. The user should make sure the Bitmap
-// is valid during the texture's lifetime. When the texture is recycled, it
-// does not free the Bitmap.
-public class BitmapTexture extends UploadedTexture {
- protected Bitmap mContentBitmap;
-
- public BitmapTexture(Bitmap bitmap) {
- this(bitmap, false);
- }
-
- public BitmapTexture(Bitmap bitmap, boolean hasBorder) {
- super(hasBorder);
- Assert.assertTrue(bitmap != null && !bitmap.isRecycled());
- mContentBitmap = bitmap;
- }
-
- @Override
- protected void onFreeBitmap(Bitmap bitmap) {
- // Do nothing.
- }
-
- @Override
- protected Bitmap onGetBitmap() {
- return mContentBitmap;
- }
-
- public Bitmap getBitmap() {
- return mContentBitmap;
- }
-}
diff --git a/src/com/android/gallery3d/glrenderer/GLCanvas.java b/src/com/android/gallery3d/glrenderer/GLCanvas.java
deleted file mode 100644
index 305e90521..000000000
--- a/src/com/android/gallery3d/glrenderer/GLCanvas.java
+++ /dev/null
@@ -1,217 +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.graphics.Bitmap;
-import android.graphics.Rect;
-import android.graphics.RectF;
-
-import javax.microedition.khronos.opengles.GL11;
-
-//
-// GLCanvas gives a convenient interface to draw using OpenGL.
-//
-// When a rectangle is specified in this interface, it means the region
-// [x, x+width) * [y, y+height)
-//
-public interface GLCanvas {
-
- public GLId getGLId();
-
- // Tells GLCanvas the size of the underlying GL surface. This should be
- // called before first drawing and when the size of GL surface is changed.
- // This is called by GLRoot and should not be called by the clients
- // who only want to draw on the GLCanvas. Both width and height must be
- // nonnegative.
- public abstract void setSize(int width, int height);
-
- // 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,
- // alpha, and clip). This call balances a previous call to save(), and is
- // used to remove all modifications to the configuration state since the
- // 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);
-
- // 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
- * GL_LINEAR for GL_TEXTURE_MIN_FILTER and GL_TEXTURE_MAG_FILTER.
- * bindTexture() must be called prior to this.
- *
- * @param texture The texture to set parameters on.
- */
- public abstract void setTextureParameters(BasicTexture texture);
-
- /**
- * Initializes the texture to a size by calling texImage2D on it.
- *
- * @param texture The texture to initialize the size.
- * @param format The texture format (e.g. GL_RGBA)
- * @param type The texture type (e.g. GL_UNSIGNED_BYTE)
- */
- public abstract void initializeTextureSize(BasicTexture texture, int format, int type);
-
- /**
- * Initializes the texture to a size by calling texImage2D on it.
- *
- * @param texture The texture to initialize the size.
- * @param bitmap The bitmap to initialize the bitmap with.
- */
- public abstract void initializeTexture(BasicTexture texture, Bitmap bitmap);
-
- /**
- * Calls glTexSubImage2D to upload a bitmap to the texture.
- *
- * @param texture The target texture to write to.
- * @param xOffset Specifies a texel offset in the x direction within the
- * texture array.
- * @param yOffset Specifies a texel offset in the y direction within the
- * texture array.
- * @param format The texture format (e.g. GL_RGBA)
- * @param type The texture type (e.g. GL_UNSIGNED_BYTE)
- */
- public abstract void texSubImage2D(BasicTexture texture, int xOffset, int yOffset,
- Bitmap bitmap,
- int format, int type);
-
- /**
- * Generates buffers and uploads the buffer data.
- *
- * @param buffer The buffer to upload
- * @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/src/com/android/gallery3d/glrenderer/GLES20Canvas.java b/src/com/android/gallery3d/glrenderer/GLES20Canvas.java
deleted file mode 100644
index 4ead1315e..000000000
--- a/src/com/android/gallery3d/glrenderer/GLES20Canvas.java
+++ /dev/null
@@ -1,1009 +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.glrenderer;
-
-import android.graphics.Bitmap;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.opengl.GLES20;
-import android.opengl.GLUtils;
-import android.opengl.Matrix;
-import android.util.Log;
-
-import com.android.gallery3d.util.IntArray;
-
-import java.nio.Buffer;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.FloatBuffer;
-import java.util.ArrayList;
-import java.util.Arrays;
-
-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 float[] BOX_COORDINATES = {
- 0, 0, // Fill rectangle
- 1, 0,
- 0, 1,
- 1, 1,
- 0, 0, // Draw line
- 1, 1,
- 0, 0, // Draw rectangle outline
- 0, 1,
- 1, 1,
- 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"
- + "uniform mat4 " + TEXTURE_MATRIX_UNIFORM + ";\n"
- + "attribute vec2 " + POSITION_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_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"
- + "uniform float " + ALPHA_UNIFORM + ";\n"
- + "uniform sampler2D " + TEXTURE_SAMPLER_UNIFORM + ";\n"
- + "void main() {\n"
- + " gl_FragColor = texture2D(" + TEXTURE_SAMPLER_UNIFORM + ", vTextureCoord);\n"
- + " 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
- private int mWidth;
- private int mHeight;
-
- // 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;
-
- // Handle indices -- common
- 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;
-
- public ShaderParameter(String name) {
- mName = name;
- }
-
- public abstract void loadHandle(int program);
- }
-
- private static class UniformShaderParameter extends ShaderParameter {
- public UniformShaderParameter(String name) {
- super(name);
- }
-
- @Override
- public void loadHandle(int program) {
- handle = GLES20.glGetUniformLocation(program, mName);
- checkError();
- }
- }
-
- private static class AttributeShaderParameter extends ShaderParameter {
- public AttributeShaderParameter(String name) {
- super(name);
- }
-
- @Override
- public void loadHandle(int program) {
- handle = GLES20.glGetAttribLocation(program, mName);
- checkError();
- }
- }
-
- ShaderParameter[] mDrawParameters = {
- new AttributeShaderParameter(POSITION_ATTRIBUTE), // INDEX_POSITION
- new UniformShaderParameter(MATRIX_UNIFORM), // INDEX_MATRIX
- new UniformShaderParameter(COLOR_UNIFORM), // INDEX_COLOR
- };
- 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];
- private final int[] mTempIntArray = new int[1];
-
- private static final GLId mGLId = new GLES20IdImpl();
-
- 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();
- }
-
- private static FloatBuffer createBuffer(float[] values) {
- // First create an nio buffer, then create a VBO from it.
- int size = values.length * FLOAT_SIZE;
- FloatBuffer buffer = ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder())
- .asFloatBuffer();
- buffer.put(values, 0, values.length).position(0);
- return buffer;
- }
-
- private int assembleProgram(int vertexShader, int fragmentShader, ShaderParameter[] params) {
- int program = GLES20.glCreateProgram();
- checkError();
- if (program == 0) {
- throw new RuntimeException("Cannot create GL program: " + GLES20.glGetError());
- }
- GLES20.glAttachShader(program, vertexShader);
- checkError();
- GLES20.glAttachShader(program, fragmentShader);
- checkError();
- GLES20.glLinkProgram(program);
- checkError();
- int[] mLinkStatus = mTempIntArray;
- GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, mLinkStatus, 0);
- if (mLinkStatus[0] != GLES20.GL_TRUE) {
- Log.e(TAG, "Could not link program: ");
- Log.e(TAG, GLES20.glGetProgramInfoLog(program));
- GLES20.glDeleteProgram(program);
- program = 0;
- }
- for (int i = 0; i < params.length; i++) {
- params[i].loadHandle(program);
- }
- return program;
- }
-
- private static int loadShader(int type, String shaderCode) {
- // create a vertex shader type (GLES20.GL_VERTEX_SHADER)
- // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
- int shader = GLES20.glCreateShader(type);
-
- // add the source code to the shader and compile it
- GLES20.glShaderSource(shader, shaderCode);
- checkError();
- GLES20.glCompileShader(shader);
- checkError();
-
- return shader;
- }
-
- @Override
- public void setSize(int width, int height) {
- mWidth = width;
- mHeight = height;
- GLES20.glViewport(0, 0, mWidth, mHeight);
- 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);
- }
- }
-
- @Override
- public void clearBuffer() {
- GLES20.glClearColor(0f, 0f, 0f, 1f);
- checkError();
- GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
- 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
- @Override
- public void translate(float x, float y) {
- int index = mCurrentMatrixIndex;
- float[] m = mMatrices;
- m[index + 12] += m[index + 0] * x + m[index + 4] * y;
- m[index + 13] += m[index + 1] * x + m[index + 5] * y;
- m[index + 14] += m[index + 2] * x + m[index + 6] * y;
- m[index + 15] += m[index + 3] * x + m[index + 7] * y;
- }
-
- @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;
- }
- float[] temp = mTempMatrix;
- Matrix.setRotateM(temp, 0, angle, x, y, z);
- float[] matrix = mMatrices;
- int index = mCurrentMatrixIndex;
- Matrix.multiplyMM(temp, MATRIX_SIZE, matrix, index, temp, 0);
- System.arraycopy(temp, MATRIX_SIZE, matrix, index, MATRIX_SIZE);
- }
-
- @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;
- mCurrentMatrixIndex += MATRIX_SIZE;
- if (mMatrices.length <= mCurrentMatrixIndex) {
- mMatrices = Arrays.copyOf(mMatrices, mMatrices.length * 2);
- }
- System.arraycopy(mMatrices, currentIndex, mMatrices, mCurrentMatrixIndex, MATRIX_SIZE);
- }
- mSaveFlags.add(saveFlags);
- }
-
- @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();
- GLES20.glVertexAttribPointer(params[INDEX_POSITION].handle, COORDS_PER_VERTEX,
- GLES20.GL_FLOAT, false, VERTEX_STRIDE, offset * VERTEX_STRIDE);
- checkError();
- GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
- checkError();
- }
-
- private void draw(ShaderParameter[] params, int type, int count, float x, float y, float width,
- float height) {
- setMatrix(params, x, y, width, height);
- int positionHandle = params[INDEX_POSITION].handle;
- GLES20.glEnableVertexAttribArray(positionHandle);
- checkError();
- GLES20.glDrawArrays(type, 0, count);
- checkError();
- GLES20.glDisableVertexAttribArray(positionHandle);
- checkError();
- }
-
- private void setMatrix(ShaderParameter[] params, float x, float y, float width, float height) {
- Matrix.translateM(mTempMatrix, 0, mMatrices, mCurrentMatrixIndex, x, y, 0f);
- Matrix.scaleM(mTempMatrix, 0, width, height, 1f);
- Matrix.multiplyMM(mTempMatrix, MATRIX_SIZE, mProjectionMatrix, 0, mTempMatrix, 0);
- GLES20.glUniformMatrix4fv(params[INDEX_MATRIX].handle, 1, false, mTempMatrix, MATRIX_SIZE);
- checkError();
- }
-
- @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;
- }
- copyTextureCoordinates(texture, mTempSourceRect);
- mTempTargetRect.set(x, y, x + width, y + height);
- convertCoordinate(mTempSourceRect, mTempTargetRect, texture);
- drawTextureRect(texture, mTempSourceRect, mTempTargetRect);
- }
-
- 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);
- }
-
- @Override
- public void drawTexture(BasicTexture texture, RectF source, RectF target) {
- if (target.width() <= 0 || target.height() <= 0) {
- return;
- }
- mTempSourceRect.set(source);
- mTempTargetRect.set(target);
-
- convertCoordinate(mTempSourceRect, mTempTargetRect, texture);
- 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);
- }
-
- private void setTextureMatrix(RectF source) {
- mTempTextureMatrix[0] = source.width();
- mTempTextureMatrix[5] = source.height();
- mTempTextureMatrix[12] = source.left;
- mTempTextureMatrix[13] = source.top;
- }
-
- // This function changes the source coordinate to the texture coordinates.
- // It also clips the source and target coordinates if it is beyond the
- // bound of the texture.
- private static void convertCoordinate(RectF source, RectF target, BasicTexture texture) {
- int width = texture.getWidth();
- int height = texture.getHeight();
- int texWidth = texture.getTextureWidth();
- int texHeight = texture.getTextureHeight();
- // Convert to texture coordinates
- source.left /= texWidth;
- source.right /= texWidth;
- source.top /= texHeight;
- source.bottom /= texHeight;
-
- // Clip if the rendering range is beyond the bound of the texture.
- float xBound = (float) width / texWidth;
- if (source.right > xBound) {
- target.right = target.left + target.width() * (xBound - source.left) / source.width();
- source.right = xBound;
- }
- float yBound = (float) height / texHeight;
- if (source.bottom > yBound) {
- target.bottom = target.top + target.height() * (yBound - source.top) / source.height();
- source.bottom = yBound;
- }
- }
-
- private void drawTextureRect(BasicTexture texture, float[] textureMatrix, RectF target) {
- ShaderParameter[] params = prepareTexture(texture);
- 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;
- }
- prepareTexture(texture, program, params);
- return params;
- }
-
- private void prepareTexture(BasicTexture texture, int program, ShaderParameter[] params) {
- GLES20.glUseProgram(program);
- checkError();
- enableBlending(!texture.isOpaque() || getAlpha() < OPAQUE_ALPHA);
- GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
- checkError();
- texture.onBind(this);
- GLES20.glBindTexture(texture.getTarget(), texture.getId());
- checkError();
- GLES20.glUniform1i(params[INDEX_TEXTURE_SAMPLER].handle, 0);
- checkError();
- GLES20.glUniform1f(params[INDEX_ALPHA].handle, getAlpha());
- checkError();
- }
-
- @Override
- public void drawMesh(BasicTexture texture, int x, int y, int xyBuffer, int uvBuffer,
- int indexBuffer, int indexCount) {
- prepareTexture(texture, mMeshProgram, mMeshParameters);
-
- GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
- checkError();
-
- GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, xyBuffer);
- checkError();
- int positionHandle = mMeshParameters[INDEX_POSITION].handle;
- GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false,
- VERTEX_STRIDE, 0);
- checkError();
-
- GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, uvBuffer);
- checkError();
- int texCoordHandle = mMeshParameters[INDEX_TEXTURE_COORD].handle;
- GLES20.glVertexAttribPointer(texCoordHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT,
- false, VERTEX_STRIDE, 0);
- checkError();
- GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
- checkError();
-
- GLES20.glEnableVertexAttribArray(positionHandle);
- checkError();
- GLES20.glEnableVertexAttribArray(texCoordHandle);
- checkError();
-
- setMatrix(mMeshParameters, x, y, 1, 1);
- GLES20.glDrawElements(GLES20.GL_TRIANGLE_STRIP, indexCount, GLES20.GL_UNSIGNED_BYTE, 0);
- 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) {
- synchronized (mUnboundTextures) {
- mUnboundTextures.add(texture.getId());
- }
- }
- return unload;
- }
-
- @Override
- public void deleteBuffer(int bufferId) {
- synchronized (mUnboundTextures) {
- mDeleteBuffers.add(bufferId);
- }
- }
-
- @Override
- public void deleteRecycledResources() {
- synchronized (mUnboundTextures) {
- IntArray ids = mUnboundTextures;
- if (mUnboundTextures.size() > 0) {
- 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());
- 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);
- }
-
- @Override
- public void initializeTextureSize(BasicTexture texture, int format, int type) {
- int target = texture.getTarget();
- GLES20.glBindTexture(target, texture.getId());
- checkError();
- int width = texture.getTextureWidth();
- int height = texture.getTextureHeight();
- GLES20.glTexImage2D(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());
- checkError();
- GLUtils.texImage2D(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());
- checkError();
- GLUtils.texSubImage2D(target, 0, xOffset, yOffset, bitmap, format, type);
- }
-
- @Override
- public int uploadBuffer(FloatBuffer buf) {
- 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();
- int bufferId = mTempIntArray[0];
- GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, bufferId);
- checkError();
- GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, buffer.capacity() * elementSize, buffer,
- GLES20.GL_STATIC_DRAW);
- checkError();
- return bufferId;
- }
-
- public static void checkError() {
- int error = GLES20.glGetError();
- if (error != 0) {
- Throwable t = new Throwable();
- Log.e(TAG, "GL error: " + error, t);
- }
- }
-
- @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/src/com/android/gallery3d/glrenderer/GLES20IdImpl.java b/src/com/android/gallery3d/glrenderer/GLES20IdImpl.java
deleted file mode 100644
index 6cd7149cb..000000000
--- a/src/com/android/gallery3d/glrenderer/GLES20IdImpl.java
+++ /dev/null
@@ -1,42 +0,0 @@
-package com.android.gallery3d.glrenderer;
-
-import android.opengl.GLES20;
-
-import javax.microedition.khronos.opengles.GL11;
-import javax.microedition.khronos.opengles.GL11ExtensionPack;
-
-public class GLES20IdImpl implements GLId {
- private final int[] mTempIntArray = new int[1];
-
- @Override
- public int generateTexture() {
- GLES20.glGenTextures(1, mTempIntArray, 0);
- GLES20Canvas.checkError();
- return mTempIntArray[0];
- }
-
- @Override
- public void glGenBuffers(int n, int[] buffers, int offset) {
- GLES20.glGenBuffers(n, buffers, offset);
- GLES20Canvas.checkError();
- }
-
- @Override
- public void glDeleteTextures(GL11 gl, int n, int[] textures, int offset) {
- GLES20.glDeleteTextures(n, textures, offset);
- GLES20Canvas.checkError();
- }
-
-
- @Override
- public void glDeleteBuffers(GL11 gl, int n, int[] buffers, int offset) {
- GLES20.glDeleteBuffers(n, buffers, offset);
- GLES20Canvas.checkError();
- }
-
- @Override
- public void glDeleteFramebuffers(GL11ExtensionPack gl11ep, int n, int[] buffers, int offset) {
- GLES20.glDeleteFramebuffers(n, buffers, offset);
- GLES20Canvas.checkError();
- }
-}
diff --git a/src/com/android/gallery3d/glrenderer/GLId.java b/src/com/android/gallery3d/glrenderer/GLId.java
deleted file mode 100644
index 3cec558f6..000000000
--- a/src/com/android/gallery3d/glrenderer/GLId.java
+++ /dev/null
@@ -1,33 +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.glrenderer;
-
-import javax.microedition.khronos.opengles.GL11;
-import javax.microedition.khronos.opengles.GL11ExtensionPack;
-
-// This mimics corresponding GL functions.
-public interface GLId {
- public int generateTexture();
-
- public void glGenBuffers(int n, int[] buffers, int offset);
-
- public void glDeleteTextures(GL11 gl, int n, int[] textures, int offset);
-
- public void glDeleteBuffers(GL11 gl, int n, int[] buffers, int offset);
-
- public void glDeleteFramebuffers(GL11ExtensionPack gl11ep, int n, int[] buffers, int offset);
-}
diff --git a/src/com/android/gallery3d/glrenderer/RawTexture.java b/src/com/android/gallery3d/glrenderer/RawTexture.java
deleted file mode 100644
index 93f0fdff9..000000000
--- a/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/src/com/android/gallery3d/glrenderer/Texture.java b/src/com/android/gallery3d/glrenderer/Texture.java
deleted file mode 100644
index 3dcae4aec..000000000
--- a/src/com/android/gallery3d/glrenderer/Texture.java
+++ /dev/null
@@ -1,44 +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;
-
-
-// Texture is a rectangular image which can be drawn on GLCanvas.
-// The isOpaque() function gives a hint about whether the texture is opaque,
-// so the drawing can be done faster.
-//
-// 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/src/com/android/gallery3d/glrenderer/UploadedTexture.java b/src/com/android/gallery3d/glrenderer/UploadedTexture.java
deleted file mode 100644
index f41a979b7..000000000
--- a/src/com/android/gallery3d/glrenderer/UploadedTexture.java
+++ /dev/null
@@ -1,298 +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.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-import android.opengl.GLUtils;
-
-import junit.framework.Assert;
-
-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
-// implement onFreeBitmap(mBitmap) which will be called when the Bitmap
-// is not needed anymore.
-//
-// isContentValid() is meaningful only when the isLoaded() returns true.
-// It means whether the content needs to be updated.
-//
-// The user of this class should call recycle() when the texture is not
-// needed anymore.
-//
-// By default an UploadedTexture is opaque (so it can be drawn faster without
-// blending). The user or subclass can override it using setOpaque().
-public abstract class UploadedTexture extends BasicTexture {
-
- // To prevent keeping allocation the borders, we store those used borders here.
- // Since the length will be power of two, it won't use too much memory.
- private static HashMap<BorderKey, Bitmap> sBorderLines =
- new HashMap<BorderKey, Bitmap>();
- private static BorderKey sBorderKey = new BorderKey();
-
- @SuppressWarnings("unused")
- private static final String TAG = "Texture";
- private boolean mContentValid = true;
-
- // 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;
- }
-
- private 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;
- 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);
- }
- return bitmap;
- }
-
- private Bitmap getBitmap() {
- if (mBitmap == null) {
- mBitmap = onGetBitmap();
- int w = mBitmap.getWidth() + mBorder * 2;
- int h = mBitmap.getHeight() + mBorder * 2;
- if (mWidth == UNSPECIFIED) {
- setSize(w, h);
- }
- }
- return mBitmap;
- }
-
- private void freeBitmap() {
- Assert.assertTrue(mBitmap != null);
- onFreeBitmap(mBitmap);
- mBitmap = null;
- }
-
- @Override
- public int getWidth() {
- if (mWidth == UNSPECIFIED) getBitmap();
- return mWidth;
- }
-
- @Override
- public int getHeight() {
- if (mWidth == UNSPECIFIED) getBitmap();
- return mHeight;
- }
-
- protected abstract Bitmap onGetBitmap();
-
- protected abstract void onFreeBitmap(Bitmap bitmap);
-
- protected void invalidateContent() {
- if (mBitmap != null) freeBitmap();
- mContentValid = false;
- mWidth = UNSPECIFIED;
- mHeight = UNSPECIFIED;
- }
-
- /**
- * Whether the content on GPU is valid.
- */
- public boolean isContentValid() {
- return isLoaded() && mContentValid;
- }
-
- /**
- * Updates the content on GPU's memory.
- * @param canvas
- */
- 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);
- 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();
-
- Assert.assertTrue(bWidth <= texWidth && bHeight <= texHeight);
-
- // Upload the bitmap to a new texture.
- mId = canvas.getGLId().generateTexture();
- canvas.setTextureParameters(this);
-
- if (bWidth == texWidth && bHeight == texHeight) {
- canvas.initializeTexture(this, bitmap);
- } else {
- int format = GLUtils.getInternalFormat(bitmap);
- int type = GLUtils.getType(bitmap);
- 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);
- }
-
- // Right border
- if (mBorder + bWidth < texWidth) {
- Bitmap line = getBorderLine(true, config, texHeight);
- canvas.texSubImage2D(this, mBorder + bWidth, 0, line, format, type);
- }
-
- // Bottom border
- if (mBorder + bHeight < texHeight) {
- Bitmap line = getBorderLine(false, config, texWidth);
- canvas.texSubImage2D(this, 0, mBorder + bHeight, line, format, type);
- }
- }
- } finally {
- freeBitmap();
- }
- // Update texture state.
- setAssociatedCanvas(canvas);
- mState = STATE_LOADED;
- mContentValid = true;
- } else {
- mState = STATE_ERROR;
- throw new RuntimeException("Texture load fail, no bitmap");
- }
- }
-
- @Override
- protected boolean onBind(GLCanvas canvas) {
- updateContent(canvas);
- return isContentValid();
- }
-
- @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/src/com/android/gallery3d/util/IntArray.java b/src/com/android/gallery3d/util/IntArray.java
deleted file mode 100644
index 2c4dc2c83..000000000
--- a/src/com/android/gallery3d/util/IntArray.java
+++ /dev/null
@@ -1,60 +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.util;
-
-public class IntArray {
- private static final int INIT_CAPACITY = 8;
-
- private int mData[] = new int[INIT_CAPACITY];
- private int mSize = 0;
-
- public void add(int value) {
- if (mData.length == mSize) {
- int temp[] = new int[mSize + mSize];
- System.arraycopy(mData, 0, temp, 0, mSize);
- mData = temp;
- }
- mData[mSize++] = value;
- }
-
- public int removeLast() {
- mSize--;
- return mData[mSize];
- }
-
- public int size() {
- return mSize;
- }
-
- // For testing only
- public int[] toArray(int[] result) {
- if (result == null || result.length < mSize) {
- result = new int[mSize];
- }
- System.arraycopy(mData, 0, result, 0, mSize);
- return result;
- }
-
- public int[] getInternalArray() {
- return mData;
- }
-
- public void clear() {
- mSize = 0;
- if (mData.length != INIT_CAPACITY) mData = new int[INIT_CAPACITY];
- }
-}
diff --git a/src/com/android/launcher/home/Home.java b/src/com/android/launcher/home/Home.java
new file mode 100644
index 000000000..5dce71e86
--- /dev/null
+++ b/src/com/android/launcher/home/Home.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2014 The CyanogenMod 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.launcher.home;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup.LayoutParams;
+
+/**
+ * The generic contract that should supports a <code>Home</code> app to could
+ * be invoked and registered by an Android launcher.<br/>
+ * <br/>
+ * This interface contains the version 1 of the <code>Home Host App</code> protocol.<br/>
+ * <br/>
+ * <br/>
+ * A <code>Home</code> app should:
+ * <ul>
+ * <li>
+ * should have at least a constructor with no arguments
+ * </li>
+ * <li>
+ * declares inside its manifest a <code>com.android.launcher.home</code> metadata with the
+ * full qualified that contains this interface<br/>
+ * <pre>
+ * &lt;meta-data android:name="com.android.launcher.home" value="org.cyanogenmod.launcher.home.HomeStub"/&gt;
+ * </pre>
+ * </li>
+ * <li>
+ * define the "com.android.launcher.home.permissions.HOME_APP" permission<br/>
+ * <pre>
+ * &lt;uses-permission android:name="com.android.launcher.home.permissions.HOME_APP"/&gt;
+ * </pre>
+ * </li>
+ * <li>
+ * implements the contract defined by this protocol.
+ * </li>
+ * </ul>
+ * <br/>
+ * Implementors classes of this protocol should be aware that all the {@link Context} references
+ * passed to this class owns to the host launcher app. This means that you cannot access
+ * to settings defined by the <code>Home</code> app inside its shared context.
+ */
+public interface Home {
+
+ /**
+ * A SHA-1 hash of all declared method of this interface. Home apps should compute as:<br/>
+ * <br/>
+ * <pre>
+ * for method in Home.class.getDeclaredMehod
+ * sha1.update method.toString.bytes
+ * </pre><br/>
+ * DO NOT MODIFY!
+ */
+ public static final String SIGNATURE = "5/A6Mxkz8gHHzzVf4qZR+hiSOAw=";
+
+ /**
+ * Defines the name of the metadata used to declared the full qualified Home stub class
+ * that implements this protocol.
+ */
+ public static final String METADATA_HOME_STUB = "com.android.launcher.home";
+
+ /**
+ * Defines the name of the permission that the Home app should explicitly declare.
+ */
+ public static final String PERMISSION_HOME_APP = "com.android.launcher.home.permissions.HOME_APP";
+
+ // Notification flags
+ public static final int FLAG_NOTIFY_MASK = 0x0000;
+ public static final int FLAG_NOTIFY_ON_RESUME = FLAG_NOTIFY_MASK + 0x0002;
+ public static final int FLAG_NOTIFY_ON_PAUSE = FLAG_NOTIFY_MASK + 0x0004;
+ public static final int FLAG_NOTIFY_ON_SHOW = FLAG_NOTIFY_MASK + 0x0008;
+ public static final int FLAG_NOTIFY_ON_SCROLL_PROGRESS_CHANGED = FLAG_NOTIFY_MASK + 0x0010;
+ public static final int FLAG_NOTIFY_ON_HIDE = FLAG_NOTIFY_MASK + 0x0020;
+ public static final int FLAG_NOTIFY_ALL = FLAG_NOTIFY_ON_RESUME | FLAG_NOTIFY_ON_PAUSE |
+ FLAG_NOTIFY_ON_SHOW | FLAG_NOTIFY_ON_SCROLL_PROGRESS_CHANGED | FLAG_NOTIFY_ON_HIDE;
+
+ // Operation support flags
+ public static final int FLAG_OP_MASK = 0x1000;
+ public static final int FLAG_OP_CUSTOM_SEARCH = FLAG_OP_MASK + 0x0002;
+ public static final int FLAG_OP_ALL = FLAG_OP_CUSTOM_SEARCH;
+
+ // Search modes
+ public static final int MODE_SEARCH_TEXT = 0x0000;
+ public static final int MODE_SEARCH_VOICE = 0x0001;
+
+ /**
+ * Invoked the first time the <code>Home</code> app is created.<br/>
+ * This method should be used by implementors classes of this protocol to load the needed
+ * resources.
+ * @param context the current {@link Context} of the host launcher.
+ */
+ void onStart(Context context);
+
+ /**
+ * Invoked when the <code>Home</code> app should be destroy.<br/>
+ * This method should be used by implementors classes of this protocol to unload all unneeded
+ * resources.
+ * @param context the current {@link Context} of the host launcher.
+ */
+ void onDestroy(Context context);
+
+ /**
+ * Invoked when the host launcher enters in resume mode.
+ * @param context the current {@link Context} of the host launcher.
+ */
+ void onResume(Context context);
+
+ /**
+ * Invoked when the host launcher enters in pause mode.
+ * @param context the current {@link Context} of the host launcher.
+ */
+ void onPause(Context context);
+
+ /**
+ * Invoked when the custom content page is totally displayed.
+ * @param context the current {@link Context} of the host launcher.
+ */
+ void onShow(Context context);
+
+ /**
+ * Invoked when the custom content page is scrolled.
+ * @param context the current {@link Context} of the host launcher.
+ * @param progress the current scroll progress.
+ */
+ void onScrollProgressChanged(Context context, float progress);
+
+ /**
+ * Invoked when the custom content page is totally hidden.
+ * @param context the current {@link Context} of the host launcher.
+ */
+ void onHide(Context context);
+
+ /**
+ * Invoked by the host launcher to request an invalidation of the ui elements and data used by
+ * the <code>Home</code> implementation class.
+ * @param context the current {@link Context} of the host launcher.
+ */
+ void onInvalidate(Context context);
+
+ /**
+ * Invoked when the host launcher request enter in search mode.
+ * @param context the current {@link Context} of the host launcher.
+ * @param mode the requested search mode. Must be one of:
+ * <ul>
+ * <li>{@link #MODE_SEARCH_TEXT}: Textual mode</li>
+ * <li>{@link #MODE_SEARCH_VOICE}: Voice mode</li>
+ * </ul>
+ */
+ void onRequestSearch(Context context, int mode);
+
+ /**
+ * Returns an instance of a {@link View} that holds the custom content to be displayed
+ * by this <code>Home</code> app.
+ * @param context the current {@link Context} of the host launcher.
+ * @return View The custom content view that will be enclosed inside a
+ * <code>com.android.launcher3.Launcher.QSBScroller</code>.<br/>
+ * Be aware the the height layout of the returned should be defined as
+ * {link {@link LayoutParams#WRAP_CONTENT}, so the view could be scrolled inside the
+ * custom content page.
+ */
+ View createCustomView(Context context);
+
+ /**
+ * Returns the name of the Home app (LIMIT: 30 characters).
+ * @param context the current {@link Context} of the host launcher.
+ */
+ String getName(Context context);
+
+ /**
+ * Implementations should return the combination of notification flags that want to listen to.
+ * @see #FLAG_NOTIFY_ON_RESUME
+ * @see #FLAG_NOTIFY_ON_PAUSE
+ * @see #FLAG_NOTIFY_ON_SHOW
+ * @see #FLAG_NOTIFY_ON_SCROLL_PROGRESS_CHANGED
+ * @see #FLAG_NOTIFY_ON_HIDE
+ * @see #FLAG_NOTIFY_ALL
+ */
+ int getNotificationFlags();
+
+ /**
+ * Implementations should return the combination of operation flags that want they want
+ * to support to.
+ * @see #FLAG_OP_CUSTOM_SEARCH
+ * @see #FLAG_OP_ALL
+ */
+ int getOperationFlags();
+}
diff --git a/src/com/android/launcher3/AddAdapter.java b/src/com/android/launcher3/AddAdapter.java
index ad15e75c6..5308a3de4 100644
--- a/src/com/android/launcher3/AddAdapter.java
+++ b/src/com/android/launcher3/AddAdapter.java
@@ -27,8 +27,6 @@ import android.widget.TextView;
import java.util.ArrayList;
-import com.android.launcher3.R;
-
/**
* Adapter showing the types of items that can be added to a {@link Workspace}.
*/
diff --git a/src/com/android/launcher3/Alarm.java b/src/com/android/launcher3/Alarm.java
index 91f9bd091..e9f1fd963 100644
--- a/src/com/android/launcher3/Alarm.java
+++ b/src/com/android/launcher3/Alarm.java
@@ -78,7 +78,3 @@ public class Alarm implements Runnable{
return mAlarmPending;
}
}
-
-interface OnAlarmListener {
- public void onAlarm(Alarm alarm);
-}
diff --git a/src/com/android/launcher3/AllAppsList.java b/src/com/android/launcher3/AllAppsList.java
index d955e4eae..89b291f28 100644
--- a/src/com/android/launcher3/AllAppsList.java
+++ b/src/com/android/launcher3/AllAppsList.java
@@ -16,9 +16,6 @@
package com.android.launcher3;
-import java.util.ArrayList;
-import java.util.List;
-
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -26,13 +23,16 @@ import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Stores the list of all applications for the all apps view.
*/
class AllAppsList {
public static final int DEFAULT_APPLICATIONS_NUMBER = 42;
-
+
/** The list off all apps. */
public ArrayList<AppInfo> data =
new ArrayList<AppInfo>(DEFAULT_APPLICATIONS_NUMBER);
@@ -115,8 +115,7 @@ class AllAppsList {
data.remove(i);
}
}
- // This is more aggressive than it needs to be.
- mIconCache.flush();
+ mIconCache.remove(packageName);
}
/**
diff --git a/src/com/android/launcher3/AppInfo.java b/src/com/android/launcher3/AppInfo.java
index 53f81bb1c..da222f11f 100644
--- a/src/com/android/launcher3/AppInfo.java
+++ b/src/com/android/launcher3/AppInfo.java
@@ -18,9 +18,9 @@ package com.android.launcher3;
import android.content.ComponentName;
import android.content.Intent;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.PackageInfo;
import android.content.pm.ResolveInfo;
import android.graphics.Bitmap;
import android.util.Log;
@@ -64,6 +64,10 @@ class AppInfo extends ItemInfo {
return intent;
}
+ protected Intent getRestoredIntent() {
+ return null;
+ }
+
/**
* Must not hold the Context.
*/
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index ba31621f1..c2125b796 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -13,8 +13,6 @@ import android.view.Gravity;
import android.widget.FrameLayout;
import android.widget.ImageView;
-import com.android.launcher3.R;
-
public class AppWidgetResizeFrame extends FrameLayout {
private LauncherAppWidgetHostView mWidgetView;
private CellLayout mCellLayout;
diff --git a/src/com/android/launcher3/AppsCustomizeLayout.java b/src/com/android/launcher3/AppsCustomizeLayout.java
index ea1c36a9c..16e5449e6 100644
--- a/src/com/android/launcher3/AppsCustomizeLayout.java
+++ b/src/com/android/launcher3/AppsCustomizeLayout.java
@@ -67,12 +67,12 @@ public class AppsCustomizeLayout extends FrameLayout implements LauncherTransiti
mContent = (FrameLayout) findViewById(R.id.apps_customize_content);
if (mAppsCustomizePane == null) throw new Resources.NotFoundException();
- findViewById(R.id.page_indicator).setOnClickListener(new OnClickListener() {
+ /*findViewById(R.id.page_indicator).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mAppsCustomizePane.enterOverviewMode();
}
- });
+ });*/
}
public boolean onInterceptTouchEvent(MotionEvent ev) {
@@ -136,6 +136,10 @@ public class AppsCustomizeLayout extends FrameLayout implements LauncherTransiti
@Override
public View getContent() {
+ View appsCustomizeContent = mAppsCustomizePane.getContent();
+ if (appsCustomizeContent != null) {
+ return appsCustomizeContent;
+ }
return mContent;
}
@@ -171,12 +175,12 @@ public class AppsCustomizeLayout extends FrameLayout implements LauncherTransiti
}
// Dismiss the workspace cling
- l.dismissWorkspaceCling(null);
+ l.getLauncherClings().dismissWorkspaceCling(null);
}
@Override
public void onLauncherTransitionStep(Launcher l, float t) {
- // Do nothing
+ mAppsCustomizePane.onLauncherTransitionStep(l, t);
}
@Override
diff --git a/src/com/android/launcher3/AppsCustomizePagedView.java b/src/com/android/launcher3/AppsCustomizePagedView.java
index ed504cf2e..f2c08a3e1 100644
--- a/src/com/android/launcher3/AppsCustomizePagedView.java
+++ b/src/com/android/launcher3/AppsCustomizePagedView.java
@@ -40,6 +40,7 @@ import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Process;
+import android.provider.Settings;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.util.Log;
@@ -170,9 +171,29 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
* The different sort modes than can be used to order items.
*/
public enum SortMode {
- Title,
- LaunchCount,
- InstallTime
+ Title(0),
+ LaunchCount(1),
+ InstallTime(2);
+
+ private final int mValue;
+ private SortMode(int value) {
+ mValue = value;
+ }
+
+ public int getValue() {
+ return mValue;
+ }
+
+ public static SortMode getModeForValue(int value) {
+ switch (value) {
+ case 1:
+ return LaunchCount;
+ case 2:
+ return InstallTime;
+ default :
+ return Title;
+ }
+ }
}
private SortMode mSortMode = SortMode.Title;
@@ -197,8 +218,8 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
private ArrayList<AppInfo> mFilteredApps;
private ArrayList<Object> mFilteredWidgets;
- private ArrayList<ComponentName> mHiddenApps;
- private ArrayList<String> mHiddenPackages;
+ private ArrayList<ComponentName> mProtectedApps;
+ private ArrayList<String> mProtectedPackages;
// Cling
private boolean mHasShownAllAppsCling;
@@ -216,6 +237,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
private PagedViewCellLayout mWidgetSpacingLayout;
private int mNumAppsPages;
private int mNumWidgetPages;
+ private Rect mAllAppsPadding = new Rect();
// Animation states
enum State { NORMAL, OVERVIEW};
@@ -236,8 +258,6 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
private boolean mOverscrollTransformsSet;
private float mLastOverscrollPivotX;
- public static boolean DISABLE_ALL_APPS = false;
-
// Previews & outlines
ArrayList<AppsCustomizeAsyncTask> mRunningTasks;
private static final int sPageSleepDelay = 200;
@@ -323,17 +343,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
}
- String[] flattened = SettingsProvider.getStringCustomDefault(context,
- SettingsProvider.SETTINGS_UI_DRAWER_HIDDEN_APPS, "").split("\\|");
- mHiddenApps = new ArrayList<ComponentName>(flattened.length);
- mHiddenPackages = new ArrayList<String>(flattened.length);
- for (String flat : flattened) {
- ComponentName cmp = ComponentName.unflattenFromString(flat);
- if (cmp != null) {
- mHiddenApps.add(cmp);
- mHiddenPackages.add(cmp.getPackageName());
- }
- }
+ updateProtectedAppsList(context);
}
@Override
@@ -361,6 +371,20 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
grid.edgeMarginPx, 2 * grid.edgeMarginPx);
}
+ void setAllAppsPadding(Rect r) {
+ mAllAppsPadding.set(r);
+ }
+ void setWidgetsPageIndicatorPadding(int pageIndicatorHeight) {
+ mPageLayoutPaddingBottom = pageIndicatorHeight;
+ }
+
+ WidgetPreviewLoader getWidgetPreviewLoader() {
+ if (mWidgetPreviewLoader == null) {
+ mWidgetPreviewLoader = new WidgetPreviewLoader(mLauncher);
+ }
+ return mWidgetPreviewLoader;
+ }
+
/** Returns the item index of the center item on this page so that we can restore to this
* item index when we rotate. */
private int getMiddleComponentIndexOnCurrentPage() {
@@ -426,10 +450,6 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
}
protected void onDataReady(int width, int height) {
- if (mWidgetPreviewLoader == null) {
- mWidgetPreviewLoader = new WidgetPreviewLoader(mLauncher);
- }
-
// Now that the data is ready, we can calculate the content width, the number of cells to
// use for each page
LauncherAppState app = LauncherAppState.getInstance();
@@ -487,7 +507,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
if (!isDataReady()) {
- if ((DISABLE_ALL_APPS || !mFilteredApps.isEmpty()) && !mFilteredWidgets.isEmpty()) {
+ if (((LauncherAppState.isDisableAllApps() || !mFilteredApps.isEmpty()) && !mFilteredWidgets.isEmpty())) {
setDataIsReady();
setMeasuredDimension(width, height);
onDataReady(width, height);
@@ -801,13 +821,13 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
int[] previewSizeBeforeScale = new int[1];
- preview = mWidgetPreviewLoader.generateWidgetPreview(createWidgetInfo.componentName,
+ preview = getWidgetPreviewLoader().generateWidgetPreview(createWidgetInfo.componentName,
createWidgetInfo.previewImage, createWidgetInfo.icon, spanX, spanY,
maxWidth, maxHeight, null, previewSizeBeforeScale);
// Compare the size of the drag preview to the preview in the AppsCustomize tray
int previewWidthInAppsCustomize = Math.min(previewSizeBeforeScale[0],
- mWidgetPreviewLoader.maxWidthForWidgetPreview(spanX));
+ getWidgetPreviewLoader().maxWidthForWidgetPreview(spanX));
scale = previewWidthInAppsCustomize / (float) preview.getWidth();
// The bitmap in the AppsCustomize tray is always the the same size, so there
@@ -888,16 +908,26 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
*/
private void endDragging(View target, boolean isFlingToDelete, boolean success) {
if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() &&
- !(target instanceof DeleteDropTarget))) {
+ !(target instanceof DeleteDropTarget) && !(target instanceof Folder))) {
// Exit spring loaded mode if we have not successfully dropped or have not handled the
// drop in Workspace
- mLauncher.exitSpringLoadedDragMode();
+ mLauncher.getWorkspace().removeExtraEmptyScreen(true, new Runnable() {
+ @Override
+ public void run() {
+ mLauncher.exitSpringLoadedDragMode();
+ mLauncher.unlockScreenOrientation(false);
+ }
+ });
+ } else {
+ mLauncher.unlockScreenOrientation(false);
}
- mLauncher.unlockScreenOrientation(false);
}
@Override
public View getContent() {
+ if (getChildCount() > 0) {
+ return getChildAt(0);
+ }
return null;
}
@@ -921,7 +951,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
mInTransition = false;
for (AsyncTaskPageData d : mDeferredSyncWidgetPageItems) {
- onSyncWidgetPageItems(d);
+ onSyncWidgetPageItems(d, false);
}
mDeferredSyncWidgetPageItems.clear();
for (Runnable r : mDeferredPrepareLoadWidgetPreviewsTasks) {
@@ -978,6 +1008,23 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
}
@Override
+ public boolean supportsAppInfoDropTarget() {
+ return true;
+ }
+
+ @Override
+ public boolean supportsDeleteDropTarget() {
+ return false;
+ }
+
+ @Override
+ public float getIntrinsicIconScaleFactor() {
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+ return (float) grid.allAppsIconSizePx / grid.iconSizePx;
+ }
+
+ @Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
cancelAllTasks();
@@ -1021,12 +1068,12 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
}
public void setContentType(ContentType type) {
- int page = getCurrentPage();
- if (mContentType != type) {
- page = 0;
+ // Widgets appear to be cleared every time you leave, always force invalidate for them
+ if (mContentType != type || type == ContentType.Widgets) {
+ int page = (mContentType != type) ? 0 : getCurrentPage();
+ mContentType = type;
+ invalidatePageData(page, true);
}
- mContentType = type;
- invalidatePageData(page, true);
}
public ContentType getContentType() {
@@ -1073,6 +1120,8 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
int heightSpec = MeasureSpec.makeMeasureSpec(mContentHeight, MeasureSpec.AT_MOST);
layout.setMinimumWidth(getPageContentWidth());
layout.measure(widthSpec, heightSpec);
+ layout.setPadding(mAllAppsPadding.left, mAllAppsPadding.top, mAllAppsPadding.right,
+ mAllAppsPadding.bottom);
setVisibilityOnChildren(layout, View.VISIBLE);
}
@@ -1127,7 +1176,6 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
icon.setOnLongClickListener(this);
icon.setOnTouchListener(this);
icon.setOnKeyListener(this);
- Utilities.applyTypeface(icon);
int index = i - startIndex;
int x = index % mCellCountX;
@@ -1230,9 +1278,9 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
mRunningTasks.remove(task);
if (task.isCancelled()) return;
// do cleanup inside onSyncWidgetPageItems
- onSyncWidgetPageItems(data);
+ onSyncWidgetPageItems(data, false);
}
- }, mWidgetPreviewLoader);
+ }, getWidgetPreviewLoader());
// Ensure that the task is appropriately prioritized and runs in parallel
AppsCustomizeAsyncTask t = new AppsCustomizeAsyncTask(page,
@@ -1293,7 +1341,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
createItemInfo.minSpanX = minSpanXY[0];
createItemInfo.minSpanY = minSpanXY[1];
- widget.applyFromAppWidgetProviderInfo(info, -1, spanXY, mWidgetPreviewLoader);
+ widget.applyFromAppWidgetProviderInfo(info, -1, spanXY, getWidgetPreviewLoader());
widget.setTag(createItemInfo);
widget.setShortPressListener(this);
} else if (rawInfo instanceof ResolveInfo) {
@@ -1303,7 +1351,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
createItemInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
createItemInfo.componentName = new ComponentName(info.activityInfo.packageName,
info.activityInfo.name);
- widget.applyFromResolveInfo(mPackageManager, info, mWidgetPreviewLoader);
+ widget.applyFromResolveInfo(mPackageManager, info, getWidgetPreviewLoader());
widget.setTag(createItemInfo);
}
widget.setOnClickListener(this);
@@ -1340,13 +1388,13 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
maxPreviewHeight = maxSize[1];
}
- mWidgetPreviewLoader.setPreviewSize(
+ getWidgetPreviewLoader().setPreviewSize(
maxPreviewWidth, maxPreviewHeight, mWidgetSpacingLayout);
if (immediate) {
AsyncTaskPageData data = new AsyncTaskPageData(page, items,
- maxPreviewWidth, maxPreviewHeight, null, null, mWidgetPreviewLoader);
+ maxPreviewWidth, maxPreviewHeight, null, null, getWidgetPreviewLoader());
loadWidgetPreviewsInBackground(null, data);
- onSyncWidgetPageItems(data);
+ onSyncWidgetPageItems(data, immediate);
} else {
if (mInTransition) {
mDeferredPrepareLoadWidgetPreviewsTasks.add(this);
@@ -1381,12 +1429,12 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
task.syncThreadPriority();
}
- images.add(mWidgetPreviewLoader.getPreview(items.get(i)));
+ images.add(getWidgetPreviewLoader().getPreview(items.get(i)));
}
}
- private void onSyncWidgetPageItems(AsyncTaskPageData data) {
- if (mInTransition) {
+ private void onSyncWidgetPageItems(AsyncTaskPageData data, boolean immediatelySyncItems) {
+ if (!immediatelySyncItems && mInTransition) {
mDeferredSyncWidgetPageItems.add(data);
return;
}
@@ -1908,6 +1956,10 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
public void setup(Launcher launcher, DragController dragController) {
mLauncher = launcher;
mDragController = dragController;
+ int sortMode = SettingsProvider.getIntCustomDefault(mLauncher, SettingsProvider.SETTINGS_UI_DRAWER_SORT_MODE, -1);
+ if (sortMode != -1) {
+ setSortMode(SortMode.getModeForValue(sortMode));
+ }
}
/**
@@ -1954,7 +2006,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
}
public void setApps(ArrayList<AppInfo> list) {
- if (!DISABLE_ALL_APPS) {
+ if (!LauncherAppState.isDisableAllApps()) {
mApps = list;
filterAppsWithoutInvalidate();
updatePageCountsAndInvalidateData();
@@ -1974,7 +2026,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
}
public void addApps(ArrayList<AppInfo> list) {
- if (!DISABLE_ALL_APPS) {
+ if (!LauncherAppState.isDisableAllApps()) {
addAppsWithoutInvalidate(list);
filterAppsWithoutInvalidate();
updatePageCountsAndInvalidateData();
@@ -2006,7 +2058,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
}
public void removeApps(ArrayList<AppInfo> appInfos) {
- if (!DISABLE_ALL_APPS) {
+ if (!LauncherAppState.isDisableAllApps()) {
removeAppsWithoutInvalidate(appInfos);
filterAppsWithoutInvalidate();
updatePageCountsAndInvalidateData();
@@ -2017,7 +2069,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
// We remove and re-add the updated applications list because it's properties may have
// changed (ie. the title), and this will ensure that the items will be in their proper
// place in the list.
- if (!DISABLE_ALL_APPS) {
+ if (!LauncherAppState.isDisableAllApps()) {
removeAppsWithoutInvalidate(list);
addAppsWithoutInvalidate(list);
filterAppsWithoutInvalidate();
@@ -2025,13 +2077,31 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
}
}
+ private void updateProtectedAppsList(Context context) {
+ String protectedComponents = Settings.Secure.getString(context.getContentResolver(),
+ LauncherModel.SETTINGS_PROTECTED_COMPONENTS);
+ protectedComponents = protectedComponents == null ? "" : protectedComponents;
+ String [] flattened = protectedComponents.split("\\|");
+ mProtectedApps = new ArrayList<ComponentName>(flattened.length);
+ mProtectedPackages = new ArrayList<String>(flattened.length);
+ for (String flat : flattened) {
+ ComponentName cmp = ComponentName.unflattenFromString(flat);
+ if (cmp != null) {
+ mProtectedApps.add(cmp);
+ mProtectedPackages.add(cmp.getPackageName());
+ }
+ }
+ }
+
public void filterAppsWithoutInvalidate() {
+ updateProtectedAppsList(mLauncher);
+
mFilteredApps = new ArrayList<AppInfo>(mApps);
Iterator<AppInfo> iterator = mFilteredApps.iterator();
while (iterator.hasNext()) {
AppInfo appInfo = iterator.next();
boolean system = (appInfo.flags & AppInfo.DOWNLOADED_FLAG) == 0;
- if (mHiddenApps.contains(appInfo.componentName) ||
+ if (mProtectedApps.contains(appInfo.componentName) ||
(system && !getShowSystemApps()) ||
(!system && !getShowDownloadedApps())) {
iterator.remove();
@@ -2046,6 +2116,8 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
}
public void filterWidgetsWithoutInvalidate() {
+ updateProtectedAppsList(mLauncher);
+
mFilteredWidgets = new ArrayList<Object>(mWidgets);
Iterator<Object> iterator = mFilteredWidgets.iterator();
@@ -2074,7 +2146,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
flags = 0;
}
boolean system = (flags & AppInfo.DOWNLOADED_FLAG) == 0;
- if (mHiddenPackages.contains(packageName) ||
+ if (mProtectedPackages.contains(packageName) ||
(system && !getShowSystemApps()) ||
(!system && !getShowDownloadedApps())) {
iterator.remove();
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 659b2b9d9..c9448850e 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -20,7 +20,6 @@ import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
-import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.Region.Op;
@@ -87,7 +86,7 @@ public class BubbleTextView extends TextView {
// Ensure we are using the right text size
LauncherAppState app = LauncherAppState.getInstance();
DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
- setTextSize(TypedValue.COMPLEX_UNIT_SP, grid.iconTextSize);
+ setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.iconTextSizePx);
setTextColor(getResources().getColor(R.color.workspace_icon_text_color));
}
@@ -111,7 +110,7 @@ public class BubbleTextView extends TextView {
Bitmap b = info.getIcon(iconCache);
setCompoundDrawables(null,
Utilities.createIconDrawable(b), null, null);
- setCompoundDrawablePadding((int) ((grid.folderIconSizePx - grid.iconSizePx) / 2f));
+ setCompoundDrawablePadding(grid.iconDrawablePaddingPx);
setText(info.title);
setTag(info);
}
@@ -203,6 +202,10 @@ public class BubbleTextView extends TextView {
destCanvas.restore();
}
+ public void setGlowColor(int color) {
+ mFocusedOutlineColor = mFocusedGlowColor = mPressedOutlineColor = mPressedGlowColor = color;
+ }
+
/**
* Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
* Responsibility for the bitmap is transferred to the caller.
diff --git a/src/com/android/launcher3/BuildInfo.java b/src/com/android/launcher3/BuildInfo.java
new file mode 100644
index 000000000..b49ee0d9b
--- /dev/null
+++ b/src/com/android/launcher3/BuildInfo.java
@@ -0,0 +1,32 @@
+package com.android.launcher3;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+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 d51ae46e8..019f86c21 100644
--- a/src/com/android/launcher3/ButtonDropTarget.java
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -25,8 +25,6 @@ import android.util.AttributeSet;
import android.view.View;
import android.widget.TextView;
-import com.android.launcher3.R;
-
/**
* Implements a DropTarget.
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index dc2db8773..373617145 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -74,6 +74,7 @@ public class CellLayout extends ViewGroup {
private int mHeightGap;
private int mMaxGap;
private boolean mScrollingTransformsDirty = false;
+ private boolean mDropPending = false;
private final Rect mRect = new Rect();
private final CellInfo mCellInfo = new CellInfo();
@@ -97,6 +98,7 @@ public class CellLayout extends ViewGroup {
private int mForegroundAlpha = 0;
private float mBackgroundAlpha;
private float mBackgroundAlphaMultiplier = 1.0f;
+ private boolean mDrawBackground = true;
private Drawable mNormalBackground;
private Drawable mActiveGlowBackground;
@@ -130,8 +132,8 @@ public class CellLayout extends ViewGroup {
private HashMap<CellLayout.LayoutParams, Animator> mReorderAnimators = new
HashMap<CellLayout.LayoutParams, Animator>();
- private HashMap<View, ReorderHintAnimation>
- mShakeAnimators = new HashMap<View, ReorderHintAnimation>();
+ private HashMap<View, ReorderPreviewAnimation>
+ mShakeAnimators = new HashMap<View, ReorderPreviewAnimation>();
private boolean mItemPlacementDirty = false;
@@ -146,19 +148,20 @@ public class CellLayout extends ViewGroup {
private boolean mIsHotseat = false;
private float mHotseatScale = 1f;
- public static final int MODE_DRAG_OVER = 0;
- public static final int MODE_ON_DROP = 1;
- public static final int MODE_ON_DROP_EXTERNAL = 2;
- public static final int MODE_ACCEPT_DROP = 3;
+ public static final int MODE_SHOW_REORDER_HINT = 0;
+ public static final int MODE_DRAG_OVER = 1;
+ public static final int MODE_ON_DROP = 2;
+ public static final int MODE_ON_DROP_EXTERNAL = 3;
+ public static final int MODE_ACCEPT_DROP = 4;
private static final boolean DESTRUCTIVE_REORDER = false;
private static final boolean DEBUG_VISUALIZE_OCCUPIED = false;
static final int LANDSCAPE = 0;
static final int PORTRAIT = 1;
- private static final float REORDER_HINT_MAGNITUDE = 0.12f;
+ private static final float REORDER_PREVIEW_MAGNITUDE = 0.12f;
private static final int REORDER_ANIMATION_DURATION = 150;
- private float mReorderHintAnimationMagnitude;
+ private float mReorderPreviewAnimationMagnitude;
private ArrayList<View> mIntersectingViews = new ArrayList<View>();
private Rect mOccupiedRect = new Rect();
@@ -212,7 +215,7 @@ public class CellLayout extends ViewGroup {
setAlwaysDrawnWithCacheEnabled(false);
final Resources res = getResources();
- mHotseatScale = (float) grid.hotseatIconSize / grid.iconSize;
+ mHotseatScale = (float) grid.hotseatIconSizePx / grid.iconSizePx;
mNormalBackground = res.getDrawable(R.drawable.screenpanel);
mActiveGlowBackground = res.getDrawable(R.drawable.screenpanel_hover);
@@ -222,7 +225,7 @@ public class CellLayout extends ViewGroup {
mForegroundPadding =
res.getDimensionPixelSize(R.dimen.workspace_overscroll_drawable_padding);
- mReorderHintAnimationMagnitude = (REORDER_HINT_MAGNITUDE *
+ mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE *
grid.iconSizePx);
mNormalBackground.setFilterBitmap(true);
@@ -330,6 +333,14 @@ public class CellLayout extends ViewGroup {
mShortcutsAndWidgets.setInvertIfRtl(invert);
}
+ public void setDropPending(boolean pending) {
+ mDropPending = pending;
+ }
+
+ public boolean isDropPending() {
+ return mDropPending;
+ }
+
private void invalidateBubbleTextView(BubbleTextView icon) {
final int padding = icon.getPressedOrFocusedBackgroundPadding();
invalidate(icon.getLeft() + getPaddingLeft() - padding,
@@ -376,6 +387,10 @@ public class CellLayout extends ViewGroup {
mUseActiveGlowBackground = use;
}
+ void disableBackground() {
+ mDrawBackground = false;
+ }
+
boolean getIsDragOverlapping() {
return mIsDragOverlapping;
}
@@ -404,7 +419,7 @@ public class CellLayout extends ViewGroup {
// When we're small, we are either drawn normally or in the "accepts drops" state (during
// a drag). However, we also drag the mini hover background *over* one of those two
// backgrounds
- if (mBackgroundAlpha > 0.0f) {
+ if (mDrawBackground && mBackgroundAlpha > 0.0f) {
Drawable bg;
if (mUseActiveGlowBackground) {
@@ -2076,6 +2091,8 @@ public class CellLayout extends ViewGroup {
}
}
+ solution.intersectingViews = new ArrayList<View>(mIntersectingViews);
+
// First we try to find a solution which respects the push mechanic. That is,
// we try to find a solution such that no displaced item travels through another item
// without also displacing that item.
@@ -2124,8 +2141,9 @@ public class CellLayout extends ViewGroup {
}
}
- ItemConfiguration simpleSwap(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
- int spanY, int[] direction, View dragView, boolean decX, ItemConfiguration solution) {
+ ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX, int minSpanY,
+ int spanX, int spanY, int[] direction, View dragView, boolean decX,
+ ItemConfiguration solution) {
// Copy the current state into the solution. This solution will be manipulated as necessary.
copyCurrentStateToSolution(solution, false);
// Copy the current occupied array into the temporary occupied array. This array will be
@@ -2147,11 +2165,11 @@ public class CellLayout extends ViewGroup {
// We try shrinking the widget down to size in an alternating pattern, shrink 1 in
// x, then 1 in y etc.
if (spanX > minSpanX && (minSpanY == spanY || decX)) {
- return simpleSwap(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY, direction,
- dragView, false, solution);
+ return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY,
+ direction, dragView, false, solution);
} else if (spanY > minSpanY) {
- return simpleSwap(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1, direction,
- dragView, true, solution);
+ return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1,
+ direction, dragView, true, solution);
}
solution.isSolution = false;
} else {
@@ -2231,25 +2249,30 @@ public class CellLayout extends ViewGroup {
}
}
- // This method starts or changes the reorder hint animations
- private void beginOrAdjustHintAnimations(ItemConfiguration solution, View dragView, int delay) {
+
+ // This method starts or changes the reorder preview animations
+ private void beginOrAdjustReorderPreviewAnimations(ItemConfiguration solution,
+ View dragView, int delay, int mode) {
int childCount = mShortcutsAndWidgets.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = mShortcutsAndWidgets.getChildAt(i);
if (child == dragView) continue;
CellAndSpan c = solution.map.get(child);
+ boolean skip = mode == ReorderPreviewAnimation.MODE_HINT && solution.intersectingViews
+ != null && !solution.intersectingViews.contains(child);
+
LayoutParams lp = (LayoutParams) child.getLayoutParams();
- if (c != null) {
- ReorderHintAnimation rha = new ReorderHintAnimation(child, lp.cellX, lp.cellY,
- c.x, c.y, c.spanX, c.spanY);
+ if (c != null && !skip) {
+ ReorderPreviewAnimation rha = new ReorderPreviewAnimation(child, mode, lp.cellX,
+ lp.cellY, c.x, c.y, c.spanX, c.spanY);
rha.animate();
}
}
}
- // Class which represents the reorder hint animations. These animations show that an item is
+ // Class which represents the reorder preview animations. These animations show that an item is
// in a temporary state, and hint at where the item will return to.
- class ReorderHintAnimation {
+ class ReorderPreviewAnimation {
View child;
float finalDeltaX;
float finalDeltaY;
@@ -2257,11 +2280,18 @@ public class CellLayout extends ViewGroup {
float initDeltaY;
float finalScale;
float initScale;
- private static final int DURATION = 300;
+ int mode;
+ boolean repeating = false;
+ private static final int PREVIEW_DURATION = 300;
+ private static final int HINT_DURATION = Workspace.REORDER_TIMEOUT;
+
+ public static final int MODE_HINT = 0;
+ public static final int MODE_PREVIEW = 1;
+
Animator a;
- public ReorderHintAnimation(View child, int cellX0, int cellY0, int cellX1, int cellY1,
- int spanX, int spanY) {
+ public ReorderPreviewAnimation(View child, int mode, int cellX0, int cellY0, int cellX1,
+ int cellY1, int spanX, int spanY) {
regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
final int x0 = mTmpPoint[0];
final int y0 = mTmpPoint[1];
@@ -2272,20 +2302,22 @@ public class CellLayout extends ViewGroup {
final int dY = y1 - y0;
finalDeltaX = 0;
finalDeltaY = 0;
+ int dir = mode == MODE_HINT ? -1 : 1;
if (dX == dY && dX == 0) {
} else {
if (dY == 0) {
- finalDeltaX = - Math.signum(dX) * mReorderHintAnimationMagnitude;
+ finalDeltaX = - dir * Math.signum(dX) * mReorderPreviewAnimationMagnitude;
} else if (dX == 0) {
- finalDeltaY = - Math.signum(dY) * mReorderHintAnimationMagnitude;
+ finalDeltaY = - dir * Math.signum(dY) * mReorderPreviewAnimationMagnitude;
} else {
double angle = Math.atan( (float) (dY) / dX);
- finalDeltaX = (int) (- Math.signum(dX) *
- Math.abs(Math.cos(angle) * mReorderHintAnimationMagnitude));
- finalDeltaY = (int) (- Math.signum(dY) *
- Math.abs(Math.sin(angle) * mReorderHintAnimationMagnitude));
+ finalDeltaX = (int) (- dir * Math.signum(dX) *
+ Math.abs(Math.cos(angle) * mReorderPreviewAnimationMagnitude));
+ finalDeltaY = (int) (- dir * Math.signum(dY) *
+ Math.abs(Math.sin(angle) * mReorderPreviewAnimationMagnitude));
}
}
+ this.mode = mode;
initDeltaX = child.getTranslationX();
initDeltaY = child.getTranslationY();
finalScale = getChildrenScale() - 4.0f / child.getWidth();
@@ -2295,7 +2327,7 @@ public class CellLayout extends ViewGroup {
void animate() {
if (mShakeAnimators.containsKey(child)) {
- ReorderHintAnimation oldAnimation = mShakeAnimators.get(child);
+ ReorderPreviewAnimation oldAnimation = mShakeAnimators.get(child);
oldAnimation.cancel();
mShakeAnimators.remove(child);
if (finalDeltaX == 0 && finalDeltaY == 0) {
@@ -2310,14 +2342,15 @@ public class CellLayout extends ViewGroup {
a = va;
va.setRepeatMode(ValueAnimator.REVERSE);
va.setRepeatCount(ValueAnimator.INFINITE);
- va.setDuration(DURATION);
+ va.setDuration(mode == MODE_HINT ? HINT_DURATION : PREVIEW_DURATION);
va.setStartDelay((int) (Math.random() * 60));
va.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float r = ((Float) animation.getAnimatedValue()).floatValue();
- float x = r * finalDeltaX + (1 - r) * initDeltaX;
- float y = r * finalDeltaY + (1 - r) * initDeltaY;
+ float r1 = (mode == MODE_HINT && repeating) ? 1.0f : r;
+ float x = r1 * finalDeltaX + (1 - r1) * initDeltaX;
+ float y = r1 * finalDeltaY + (1 - r1) * initDeltaY;
child.setTranslationX(x);
child.setTranslationY(y);
float s = r * finalScale + (1 - r) * initScale;
@@ -2331,6 +2364,7 @@ public class CellLayout extends ViewGroup {
initDeltaX = 0;
initDeltaY = 0;
initScale = getChildrenScale();
+ repeating = true;
}
});
mShakeAnimators.put(child, this);
@@ -2362,8 +2396,8 @@ public class CellLayout extends ViewGroup {
}
}
- private void completeAndClearReorderHintAnimations() {
- for (ReorderHintAnimation a: mShakeAnimators.values()) {
+ private void completeAndClearReorderPreviewAnimations() {
+ for (ReorderPreviewAnimation a: mShakeAnimators.values()) {
a.completeAnimationImmediately();
}
mShakeAnimators.clear();
@@ -2506,20 +2540,21 @@ public class CellLayout extends ViewGroup {
}
void revertTempState() {
- if (!isItemPlacementDirty() || DESTRUCTIVE_REORDER) return;
- final int count = mShortcutsAndWidgets.getChildCount();
- for (int i = 0; i < count; i++) {
- View child = mShortcutsAndWidgets.getChildAt(i);
- LayoutParams lp = (LayoutParams) child.getLayoutParams();
- if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
- lp.tmpCellX = lp.cellX;
- lp.tmpCellY = lp.cellY;
- animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION,
- 0, false, false);
+ completeAndClearReorderPreviewAnimations();
+ if (isItemPlacementDirty() && !DESTRUCTIVE_REORDER) {
+ final int count = mShortcutsAndWidgets.getChildCount();
+ for (int i = 0; i < count; i++) {
+ View child = mShortcutsAndWidgets.getChildAt(i);
+ LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
+ lp.tmpCellX = lp.cellX;
+ lp.tmpCellY = lp.cellY;
+ animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION,
+ 0, false, false);
+ }
}
+ setItemPlacementDirty(false);
}
- completeAndClearReorderHintAnimations();
- setItemPlacementDirty(false);
}
boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY,
@@ -2528,7 +2563,7 @@ public class CellLayout extends ViewGroup {
regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY);
// First we determine if things have moved enough to cause a different layout
- ItemConfiguration swapSolution = simpleSwap(pixelXY[0], pixelXY[1], spanX, spanY,
+ ItemConfiguration swapSolution = findReorderSolution(pixelXY[0], pixelXY[1], spanX, spanY,
spanX, spanY, direction, dragView, true, new ItemConfiguration());
setUseTempCoords(true);
@@ -2542,18 +2577,18 @@ public class CellLayout extends ViewGroup {
if (commit) {
commitTempPlacement();
- completeAndClearReorderHintAnimations();
+ completeAndClearReorderPreviewAnimations();
setItemPlacementDirty(false);
} else {
- beginOrAdjustHintAnimations(swapSolution, dragView,
- REORDER_ANIMATION_DURATION);
+ beginOrAdjustReorderPreviewAnimations(swapSolution, dragView,
+ REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
}
mShortcutsAndWidgets.requestLayout();
}
return swapSolution.isSolution;
}
- int[] createArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
+ int[] performReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
View dragView, int[] result, int resultSpan[], int mode) {
// First we determine if things have moved enough to cause a different layout
result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
@@ -2580,7 +2615,8 @@ public class CellLayout extends ViewGroup {
mPreviousReorderDirection[1] = mDirectionVector[1];
}
- ItemConfiguration swapSolution = simpleSwap(pixelX, pixelY, minSpanX, minSpanY,
+ // Find a solution involving pushing / displacing any items in the way
+ ItemConfiguration swapSolution = findReorderSolution(pixelX, pixelY, minSpanX, minSpanY,
spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration());
// We attempt the approach which doesn't shuffle views at all
@@ -2588,12 +2624,29 @@ public class CellLayout extends ViewGroup {
minSpanY, spanX, spanY, dragView, new ItemConfiguration());
ItemConfiguration finalSolution = null;
+
+ // If the reorder solution requires resizing (shrinking) the item being dropped, we instead
+ // favor a solution in which the item is not resized, but
if (swapSolution.isSolution && swapSolution.area() >= noShuffleSolution.area()) {
finalSolution = swapSolution;
} else if (noShuffleSolution.isSolution) {
finalSolution = noShuffleSolution;
}
+ if (mode == MODE_SHOW_REORDER_HINT) {
+ if (finalSolution != null) {
+ beginOrAdjustReorderPreviewAnimations(finalSolution, dragView, 0,
+ ReorderPreviewAnimation.MODE_HINT);
+ result[0] = finalSolution.dragViewX;
+ result[1] = finalSolution.dragViewY;
+ resultSpan[0] = finalSolution.dragViewSpanX;
+ resultSpan[1] = finalSolution.dragViewSpanY;
+ } else {
+ result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
+ }
+ return result;
+ }
+
boolean foundSolution = true;
if (!DESTRUCTIVE_REORDER) {
setUseTempCoords(true);
@@ -2618,11 +2671,11 @@ public class CellLayout extends ViewGroup {
if (!DESTRUCTIVE_REORDER &&
(mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
commitTempPlacement();
- completeAndClearReorderHintAnimations();
+ completeAndClearReorderPreviewAnimations();
setItemPlacementDirty(false);
} else {
- beginOrAdjustHintAnimations(finalSolution, dragView,
- REORDER_ANIMATION_DURATION);
+ beginOrAdjustReorderPreviewAnimations(finalSolution, dragView,
+ REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
}
}
} else {
@@ -2649,6 +2702,7 @@ public class CellLayout extends ViewGroup {
HashMap<View, CellAndSpan> map = new HashMap<View, CellAndSpan>();
private HashMap<View, CellAndSpan> savedMap = new HashMap<View, CellAndSpan>();
ArrayList<View> sortedViews = new ArrayList<View>();
+ ArrayList<View> intersectingViews;
boolean isSolution = false;
int dragViewX, dragViewY, dragViewSpanX, dragViewSpanY;
@@ -3108,7 +3162,8 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
if (x < mCountX && y < mCountY) {
return mOccupied[x][y];
} else {
- throw new RuntimeException("Position exceeds the bound of this CellLayout");
+ Log.w(TAG, "Position exceeds the bound of this CellLayout");
+ return false;
}
}
diff --git a/src/com/android/launcher3/CheckableFrameLayout.java b/src/com/android/launcher3/CheckableFrameLayout.java
deleted file mode 100644
index 5b7d82425..000000000
--- a/src/com/android/launcher3/CheckableFrameLayout.java
+++ /dev/null
@@ -1,63 +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.Context;
-import android.util.AttributeSet;
-import android.widget.Checkable;
-import android.widget.FrameLayout;
-
-public class CheckableFrameLayout extends FrameLayout implements Checkable {
- private static final int[] CHECKED_STATE_SET = { android.R.attr.state_checked };
- boolean mChecked;
-
- public CheckableFrameLayout(Context context) {
- super(context);
- }
-
- public CheckableFrameLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public CheckableFrameLayout(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- public boolean isChecked() {
- return mChecked;
- }
-
- public void setChecked(boolean checked) {
- if (checked != mChecked) {
- mChecked = checked;
- refreshDrawableState();
- }
- }
-
- public void toggle() {
- setChecked(!mChecked);
- }
-
- @Override
- protected int[] onCreateDrawableState(int extraSpace) {
- final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
- if (isChecked()) {
- mergeDrawableStates(drawableState, CHECKED_STATE_SET);
- }
- return drawableState;
- }
-}
diff --git a/src/com/android/launcher3/Cling.java b/src/com/android/launcher3/Cling.java
index 01a54b4e1..a6139ccbc 100644
--- a/src/com/android/launcher3/Cling.java
+++ b/src/com/android/launcher3/Cling.java
@@ -18,8 +18,8 @@ package com.android.launcher3;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.content.Context;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -30,6 +30,7 @@ import android.util.DisplayMetrics;
import android.view.FocusFinder;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.widget.FrameLayout;
import android.widget.TextView;
@@ -37,11 +38,6 @@ import android.widget.TextView;
public class Cling extends FrameLayout implements Insettable, View.OnClickListener,
View.OnLongClickListener, View.OnTouchListener {
- static final String FIRST_RUN_CLING_DISMISSED_KEY = "cling_gel.first_run.dismissed";
- static final String WORKSPACE_CLING_DISMISSED_KEY = "cling_gel.workspace.dismissed";
- static final String FOLDER_CLING_DISMISSED_KEY = "cling_gel.folder.dismissed";
- static final String ALL_APPS_CLING_DISMISSED_KEY = "cling_gel.all_apps.dismissed";
-
private static String FIRST_RUN_PORTRAIT = "first_run_portrait";
private static String FIRST_RUN_LANDSCAPE = "first_run_landscape";
@@ -50,16 +46,24 @@ public class Cling extends FrameLayout implements Insettable, View.OnClickListen
private static String WORKSPACE_LARGE = "workspace_large";
private static String WORKSPACE_CUSTOM = "workspace_custom";
+ private static String MIGRATION_PORTRAIT = "migration_portrait";
+ private static String MIGRATION_LANDSCAPE = "migration_landscape";
+
+ private static String MIGRATION_WORKSPACE_PORTRAIT = "migration_workspace_portrait";
+ private static String MIGRATION_WORKSPACE_LARGE_PORTRAIT = "migration_workspace_large_portrait";
+ private static String MIGRATION_WORKSPACE_LANDSCAPE = "migration_workspace_landscape";
+
private static String FOLDER_PORTRAIT = "folder_portrait";
private static String FOLDER_LANDSCAPE = "folder_landscape";
private static String FOLDER_LARGE = "folder_large";
- private static String ALL_APPS = "all_apps";
-
private static float FIRST_RUN_CIRCLE_BUFFER_DPS = 60;
+ private static float FIRST_RUN_MAX_CIRCLE_RADIUS_DPS = 180;
private static float WORKSPACE_INNER_CIRCLE_RADIUS_DPS = 50;
private static float WORKSPACE_OUTER_CIRCLE_RADIUS_DPS = 60;
private static float WORKSPACE_CIRCLE_Y_OFFSET_DPS = 30;
+ private static float MIGRATION_WORKSPACE_INNER_CIRCLE_RADIUS_DPS = 42;
+ private static float MIGRATION_WORKSPACE_OUTER_CIRCLE_RADIUS_DPS = 46;
private Launcher mLauncher;
private boolean mIsInitialized;
@@ -73,6 +77,7 @@ public class Cling extends FrameLayout implements Insettable, View.OnClickListen
private Rect mFocusedHotseatAppBounds;
private Paint mErasePaint;
+ private Paint mBorderPaint;
private Paint mBubblePaint;
private Paint mDotPaint;
@@ -80,7 +85,6 @@ public class Cling extends FrameLayout implements Insettable, View.OnClickListen
private int mBackgroundColor;
private final Rect mInsets = new Rect();
- private int[] mPosition;
public Cling(Context context) {
this(context, null, 0);
@@ -105,7 +109,7 @@ public class Cling extends FrameLayout implements Insettable, View.OnClickListen
if (!mIsInitialized) {
mLauncher = l;
mScrimView = scrim;
- mBackgroundColor = 0xdd000000;
+ mBackgroundColor = 0xcc000000;
setOnLongClickListener(this);
setOnClickListener(this);
setOnTouchListener(this);
@@ -116,6 +120,10 @@ public class Cling extends FrameLayout implements Insettable, View.OnClickListen
mErasePaint.setAlpha(0);
mErasePaint.setAntiAlias(true);
+ mBorderPaint = new Paint();
+ mBorderPaint.setColor(0xFFFFFFFF);
+ mBorderPaint.setAntiAlias(true);
+
int circleColor = getResources().getColor(
R.color.first_run_cling_circle_background_color);
mBubblePaint = new Paint();
@@ -130,20 +138,15 @@ public class Cling extends FrameLayout implements Insettable, View.OnClickListen
}
}
- void setPunchThroughForView(View view) {
- mPosition = new int[2];
- view.getLocationOnScreen(mPosition);
- mPosition[0] += view.getWidth() / 2;
- mPosition[1] += view.getHeight() / 2;
- }
-
void setFocusedHotseatApp(int drawableId, int appRank, ComponentName cn, String title,
String description) {
// Get the app to draw
Resources r = getResources();
int appIconId = drawableId;
Hotseat hotseat = mLauncher.getHotseat();
- if (hotseat != null && appIconId > -1 && appRank > -1 && !title.isEmpty() &&
+ // Skip the focused app in the large layouts
+ if (!mDrawIdentifier.equals(WORKSPACE_LARGE) &&
+ hotseat != null && appIconId > -1 && appRank > -1 && !title.isEmpty() &&
!description.isEmpty()) {
// Set the app bounds
int x = hotseat.getCellXFromOrder(appRank);
@@ -157,7 +160,7 @@ public class Cling extends FrameLayout implements Insettable, View.OnClickListen
pos.left + Utilities.sIconTextureWidth,
pos.top + Utilities.sIconTextureHeight);
Utilities.scaleRectAboutCenter(mFocusedHotseatAppBounds,
- (grid.hotseatIconSize / grid.iconSize));
+ ((float) grid.hotseatIconSizePx / grid.iconSizePx));
// Set the title
TextView v = (TextView) findViewById(R.id.focused_hotseat_app_title);
@@ -177,6 +180,82 @@ public class Cling extends FrameLayout implements Insettable, View.OnClickListen
}
}
+ void setOpenFolderRect(Rect r) {
+ if (mDrawIdentifier.equals(FOLDER_LANDSCAPE) ||
+ mDrawIdentifier.equals(FOLDER_LARGE)) {
+ ViewGroup vg = (ViewGroup) findViewById(R.id.folder_bubble);
+ ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) vg.getLayoutParams();
+ lp.topMargin = r.top - mInsets.bottom;
+ lp.leftMargin = r.right;
+ vg.setLayoutDirection(View.LAYOUT_DIRECTION_LTR);
+ vg.requestLayout();
+ }
+ }
+
+ void updateMigrationWorkspaceBubblePosition() {
+ DisplayMetrics metrics = new DisplayMetrics();
+ mLauncher.getWindowManager().getDefaultDisplay().getMetrics(metrics);
+
+ // Get the page indicator bounds
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+ Rect pageIndicatorBounds = grid.getWorkspacePageIndicatorBounds(mInsets);
+
+ if (mDrawIdentifier.equals(MIGRATION_WORKSPACE_PORTRAIT)) {
+ View bubble = findViewById(R.id.migration_workspace_cling_bubble);
+ ViewGroup.MarginLayoutParams lp =
+ (ViewGroup.MarginLayoutParams) bubble.getLayoutParams();
+ lp.bottomMargin = grid.heightPx - pageIndicatorBounds.top;
+ bubble.requestLayout();
+ } else if (mDrawIdentifier.equals(MIGRATION_WORKSPACE_LARGE_PORTRAIT)) {
+ View bubble = findViewById(R.id.content);
+ ViewGroup.MarginLayoutParams lp =
+ (ViewGroup.MarginLayoutParams) bubble.getLayoutParams();
+ lp.bottomMargin = grid.heightPx - pageIndicatorBounds.top;
+ bubble.requestLayout();
+ } else if (mDrawIdentifier.equals(MIGRATION_WORKSPACE_LANDSCAPE)) {
+ View bubble = findViewById(R.id.content);
+ ViewGroup.MarginLayoutParams lp =
+ (ViewGroup.MarginLayoutParams) bubble.getLayoutParams();
+ if (grid.isLayoutRtl) {
+ lp.leftMargin = pageIndicatorBounds.right;
+ } else {
+ lp.rightMargin = (grid.widthPx - pageIndicatorBounds.left);
+ }
+ bubble.requestLayout();
+ }
+ }
+
+ void updateWorkspaceBubblePosition() {
+ DisplayMetrics metrics = new DisplayMetrics();
+ mLauncher.getWindowManager().getDefaultDisplay().getMetrics(metrics);
+
+ // Get the cut-out bounds
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+ Rect cutOutBounds = getWorkspaceCutOutBounds(metrics);
+
+ if (mDrawIdentifier.equals(WORKSPACE_LARGE)) {
+ View bubble = findViewById(R.id.workspace_cling_bubble);
+ ViewGroup.MarginLayoutParams lp =
+ (ViewGroup.MarginLayoutParams) bubble.getLayoutParams();
+ lp.bottomMargin = grid.heightPx - cutOutBounds.top - mInsets.bottom;
+ bubble.requestLayout();
+ }
+ }
+
+ private Rect getWorkspaceCutOutBounds(DisplayMetrics metrics) {
+ int halfWidth = metrics.widthPixels / 2;
+ int halfHeight = metrics.heightPixels / 2;
+ int yOffset = DynamicGrid.pxFromDp(WORKSPACE_CIRCLE_Y_OFFSET_DPS, metrics);
+ if (mDrawIdentifier.equals(WORKSPACE_LARGE)) {
+ yOffset = 0;
+ }
+ int radius = DynamicGrid.pxFromDp(WORKSPACE_OUTER_CIRCLE_RADIUS_DPS, metrics);
+ return new Rect(halfWidth - radius, halfHeight - yOffset - radius, halfWidth + radius,
+ halfHeight - yOffset + radius);
+ }
+
void show(boolean animate, int duration) {
setVisibility(View.VISIBLE);
setLayerType(View.LAYER_TYPE_HARDWARE, null);
@@ -184,7 +263,9 @@ public class Cling extends FrameLayout implements Insettable, View.OnClickListen
mDrawIdentifier.equals(WORKSPACE_LANDSCAPE) ||
mDrawIdentifier.equals(WORKSPACE_LARGE) ||
mDrawIdentifier.equals(WORKSPACE_CUSTOM) ||
- mDrawIdentifier.equals(ALL_APPS)) {
+ mDrawIdentifier.equals(MIGRATION_WORKSPACE_PORTRAIT) ||
+ mDrawIdentifier.equals(MIGRATION_WORKSPACE_LARGE_PORTRAIT) ||
+ mDrawIdentifier.equals(MIGRATION_WORKSPACE_LANDSCAPE)) {
View content = getContent();
content.setAlpha(0f);
content.animate()
@@ -230,7 +311,9 @@ public class Cling extends FrameLayout implements Insettable, View.OnClickListen
void hide(final int duration, final Runnable postCb) {
if (mDrawIdentifier.equals(FIRST_RUN_PORTRAIT) ||
- mDrawIdentifier.equals(FIRST_RUN_LANDSCAPE)) {
+ mDrawIdentifier.equals(FIRST_RUN_LANDSCAPE) ||
+ mDrawIdentifier.equals(MIGRATION_PORTRAIT) ||
+ mDrawIdentifier.equals(MIGRATION_LANDSCAPE)) {
View content = getContent();
content.animate()
.alpha(0f)
@@ -311,8 +394,7 @@ public class Cling extends FrameLayout implements Insettable, View.OnClickListen
return (mDrawIdentifier.equals(WORKSPACE_PORTRAIT)
|| mDrawIdentifier.equals(WORKSPACE_LANDSCAPE)
|| mDrawIdentifier.equals(WORKSPACE_LARGE)
- || mDrawIdentifier.equals(WORKSPACE_CUSTOM)
- || mDrawIdentifier.equals(ALL_APPS));
+ || mDrawIdentifier.equals(WORKSPACE_CUSTOM));
}
@Override
@@ -353,7 +435,7 @@ public class Cling extends FrameLayout implements Insettable, View.OnClickListen
intent.setComponent(mFocusedHotseatAppComponent);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
mLauncher.startActivity(intent, null);
- mLauncher.dismissWorkspaceCling(this);
+ mLauncher.getLauncherClings().dismissWorkspaceCling(this);
}
}
}
@@ -363,10 +445,12 @@ public class Cling extends FrameLayout implements Insettable, View.OnClickListen
if (mDrawIdentifier.equals(WORKSPACE_PORTRAIT) ||
mDrawIdentifier.equals(WORKSPACE_LANDSCAPE) ||
mDrawIdentifier.equals(WORKSPACE_LARGE)) {
- mLauncher.dismissWorkspaceCling(null);
+ mLauncher.getLauncherClings().dismissWorkspaceCling(null);
return true;
- } else if (mDrawIdentifier.equals(ALL_APPS)) {
- mLauncher.dismissAllAppsCling(null);
+ } else if (mDrawIdentifier.equals(MIGRATION_WORKSPACE_PORTRAIT) ||
+ mDrawIdentifier.equals(MIGRATION_WORKSPACE_LARGE_PORTRAIT) ||
+ mDrawIdentifier.equals(MIGRATION_WORKSPACE_LANDSCAPE)) {
+ mLauncher.getLauncherClings().dismissMigrationWorkspaceCling(null);
return true;
}
return false;
@@ -377,6 +461,11 @@ public class Cling extends FrameLayout implements Insettable, View.OnClickListen
if (mIsInitialized) {
canvas.save();
+ // Get the page indicator bounds
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+ Rect pageIndicatorBounds = grid.getWorkspacePageIndicatorBounds(mInsets);
+
// Get the background override if there is one
if (mBackground == null) {
if (mDrawIdentifier.equals(WORKSPACE_CUSTOM)) {
@@ -395,7 +484,9 @@ public class Cling extends FrameLayout implements Insettable, View.OnClickListen
} else if (mDrawIdentifier.equals(WORKSPACE_PORTRAIT) ||
mDrawIdentifier.equals(WORKSPACE_LANDSCAPE) ||
mDrawIdentifier.equals(WORKSPACE_LARGE) ||
- mDrawIdentifier.equals(ALL_APPS)) {
+ mDrawIdentifier.equals(MIGRATION_WORKSPACE_PORTRAIT) ||
+ mDrawIdentifier.equals(MIGRATION_WORKSPACE_LARGE_PORTRAIT) ||
+ mDrawIdentifier.equals(MIGRATION_WORKSPACE_LANDSCAPE)) {
// Initialize the draw buffer (to allow punching through)
eraseBg = Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(),
Bitmap.Config.ARGB_8888);
@@ -421,29 +512,23 @@ public class Cling extends FrameLayout implements Insettable, View.OnClickListen
bubbleContent.getGlobalVisibleRect(bubbleRect);
mBubblePaint.setAlpha((int) (255 * alpha));
float buffer = DynamicGrid.pxFromDp(FIRST_RUN_CIRCLE_BUFFER_DPS, metrics);
+ float maxRadius = DynamicGrid.pxFromDp(FIRST_RUN_MAX_CIRCLE_RADIUS_DPS, metrics);
+ float radius = Math.min(maxRadius, (bubbleContent.getMeasuredWidth() + buffer) / 2);
canvas.drawCircle(metrics.widthPixels / 2,
- bubbleRect.centerY(),
- (bubbleContent.getMeasuredWidth() + buffer) / 2,
+ bubbleRect.centerY(), radius,
mBubblePaint);
} else if (mDrawIdentifier.equals(WORKSPACE_PORTRAIT) ||
mDrawIdentifier.equals(WORKSPACE_LANDSCAPE) ||
- mDrawIdentifier.equals(WORKSPACE_LARGE) ||
- mDrawIdentifier.equals(ALL_APPS)) {
- int offset = DynamicGrid.pxFromDp(WORKSPACE_CIRCLE_Y_OFFSET_DPS, metrics);
- mErasePaint.setAlpha((int) (128));
- int punchX = metrics.widthPixels / 2;
- int punchY = metrics.heightPixels / 2 - offset;
- if (mPosition != null) {
- punchX = mPosition[0];
- punchY = mPosition[1];
- }
- eraseCanvas.drawCircle(punchX,
- punchY,
+ mDrawIdentifier.equals(WORKSPACE_LARGE)) {
+ Rect cutOutBounds = getWorkspaceCutOutBounds(metrics);
+ // Draw the outer circle
+ mErasePaint.setAlpha(128);
+ eraseCanvas.drawCircle(cutOutBounds.centerX(), cutOutBounds.centerY(),
DynamicGrid.pxFromDp(WORKSPACE_OUTER_CIRCLE_RADIUS_DPS, metrics),
mErasePaint);
+ // Draw the inner circle
mErasePaint.setAlpha(0);
- eraseCanvas.drawCircle(punchX,
- punchY,
+ eraseCanvas.drawCircle(cutOutBounds.centerX(), cutOutBounds.centerY(),
DynamicGrid.pxFromDp(WORKSPACE_INNER_CIRCLE_RADIUS_DPS, metrics),
mErasePaint);
canvas.drawBitmap(eraseBg, 0, 0, null);
@@ -458,8 +543,25 @@ public class Cling extends FrameLayout implements Insettable, View.OnClickListen
mFocusedHotseatApp.setAlpha((int) (255 * alpha));
mFocusedHotseatApp.draw(canvas);
}
+ } else if (mDrawIdentifier.equals(MIGRATION_WORKSPACE_PORTRAIT) ||
+ mDrawIdentifier.equals(MIGRATION_WORKSPACE_LARGE_PORTRAIT) ||
+ mDrawIdentifier.equals(MIGRATION_WORKSPACE_LANDSCAPE)) {
+ int offset = DynamicGrid.pxFromDp(WORKSPACE_CIRCLE_Y_OFFSET_DPS, metrics);
+ // Draw the outer circle
+ eraseCanvas.drawCircle(pageIndicatorBounds.centerX(),
+ pageIndicatorBounds.centerY(),
+ DynamicGrid.pxFromDp(MIGRATION_WORKSPACE_OUTER_CIRCLE_RADIUS_DPS, metrics),
+ mBorderPaint);
+ // Draw the inner circle
+ mErasePaint.setAlpha(0);
+ eraseCanvas.drawCircle(pageIndicatorBounds.centerX(),
+ pageIndicatorBounds.centerY(),
+ DynamicGrid.pxFromDp(MIGRATION_WORKSPACE_INNER_CIRCLE_RADIUS_DPS, metrics),
+ mErasePaint);
+ canvas.drawBitmap(eraseBg, 0, 0, null);
+ eraseCanvas.setBitmap(null);
+ eraseBg = null;
}
-
canvas.restore();
}
diff --git a/src/com/android/launcher3/CropView.java b/src/com/android/launcher3/CropView.java
deleted file mode 100644
index 9224e3bb2..000000000
--- a/src/com/android/launcher3/CropView.java
+++ /dev/null
@@ -1,321 +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.Context;
-import android.graphics.Matrix;
-import android.graphics.Point;
-import android.graphics.RectF;
-import android.util.AttributeSet;
-import android.util.FloatMath;
-import android.view.MotionEvent;
-import android.view.ScaleGestureDetector;
-import android.view.ScaleGestureDetector.OnScaleGestureListener;
-import android.view.ViewConfiguration;
-import android.view.ViewTreeObserver;
-import android.view.ViewTreeObserver.OnGlobalLayoutListener;
-
-import com.android.photos.views.TiledImageRenderer.TileSource;
-import com.android.photos.views.TiledImageView;
-
-public class CropView extends TiledImageView implements OnScaleGestureListener {
-
- private ScaleGestureDetector mScaleGestureDetector;
- private long mTouchDownTime;
- private float mFirstX, mFirstY;
- private float mLastX, mLastY;
- private float mCenterX, mCenterY;
- private float mMinScale;
- private boolean mTouchEnabled = true;
- private RectF mTempEdges = new RectF();
- private float[] mTempPoint = new float[] { 0, 0 };
- private float[] mTempCoef = new float[] { 0, 0 };
- private float[] mTempAdjustment = new float[] { 0, 0 };
- private float[] mTempImageDims = new float[] { 0, 0 };
- private float[] mTempRendererCenter = new float[] { 0, 0 };
- TouchCallback mTouchCallback;
- Matrix mRotateMatrix;
- Matrix mInverseRotateMatrix;
-
- public interface TouchCallback {
- void onTouchDown();
- void onTap();
- void onTouchUp();
- }
-
- public CropView(Context context) {
- this(context, null);
- }
-
- public CropView(Context context, AttributeSet attrs) {
- super(context, attrs);
- mScaleGestureDetector = new ScaleGestureDetector(context, this);
- mRotateMatrix = new Matrix();
- mInverseRotateMatrix = new Matrix();
- }
-
- private float[] getImageDims() {
- final float imageWidth = mRenderer.source.getImageWidth();
- final float imageHeight = mRenderer.source.getImageHeight();
- float[] imageDims = mTempImageDims;
- imageDims[0] = imageWidth;
- imageDims[1] = imageHeight;
- mRotateMatrix.mapPoints(imageDims);
- imageDims[0] = Math.abs(imageDims[0]);
- imageDims[1] = Math.abs(imageDims[1]);
- return imageDims;
- }
-
- private void getEdgesHelper(RectF edgesOut) {
- final float width = getWidth();
- final float height = getHeight();
- final float[] imageDims = getImageDims();
- final float imageWidth = imageDims[0];
- final float imageHeight = imageDims[1];
-
- float initialCenterX = mRenderer.source.getImageWidth() / 2f;
- float initialCenterY = mRenderer.source.getImageHeight() / 2f;
-
- float[] rendererCenter = mTempRendererCenter;
- rendererCenter[0] = mCenterX - initialCenterX;
- rendererCenter[1] = mCenterY - initialCenterY;
- mRotateMatrix.mapPoints(rendererCenter);
- rendererCenter[0] += imageWidth / 2;
- rendererCenter[1] += imageHeight / 2;
-
- final float scale = mRenderer.scale;
- float centerX = (width / 2f - rendererCenter[0] + (imageWidth - width) / 2f)
- * scale + width / 2f;
- float centerY = (height / 2f - rendererCenter[1] + (imageHeight - height) / 2f)
- * scale + height / 2f;
- float leftEdge = centerX - imageWidth / 2f * scale;
- float rightEdge = centerX + imageWidth / 2f * scale;
- float topEdge = centerY - imageHeight / 2f * scale;
- float bottomEdge = centerY + imageHeight / 2f * scale;
-
- edgesOut.left = leftEdge;
- edgesOut.right = rightEdge;
- edgesOut.top = topEdge;
- edgesOut.bottom = bottomEdge;
- }
-
- public int getImageRotation() {
- return mRenderer.rotation;
- }
-
- public RectF getCrop() {
- final RectF edges = mTempEdges;
- getEdgesHelper(edges);
- final float scale = mRenderer.scale;
-
- float cropLeft = -edges.left / scale;
- float cropTop = -edges.top / scale;
- float cropRight = cropLeft + getWidth() / scale;
- float cropBottom = cropTop + getHeight() / scale;
-
- return new RectF(cropLeft, cropTop, cropRight, cropBottom);
- }
-
- public Point getSourceDimensions() {
- return new Point(mRenderer.source.getImageWidth(), mRenderer.source.getImageHeight());
- }
-
- public void setTileSource(TileSource source, Runnable isReadyCallback) {
- super.setTileSource(source, isReadyCallback);
- mCenterX = mRenderer.centerX;
- mCenterY = mRenderer.centerY;
- mRotateMatrix.reset();
- mRotateMatrix.setRotate(mRenderer.rotation);
- mInverseRotateMatrix.reset();
- mInverseRotateMatrix.setRotate(-mRenderer.rotation);
- updateMinScale(getWidth(), getHeight(), source, true);
- }
-
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- updateMinScale(w, h, mRenderer.source, false);
- }
-
- public void setScale(float scale) {
- synchronized (mLock) {
- mRenderer.scale = scale;
- }
- }
-
- private void updateMinScale(int w, int h, TileSource source, boolean resetScale) {
- synchronized (mLock) {
- if (resetScale) {
- mRenderer.scale = 1;
- }
- if (source != null) {
- final float[] imageDims = getImageDims();
- final float imageWidth = imageDims[0];
- final float imageHeight = imageDims[1];
- mMinScale = Math.max(w / imageWidth, h / imageHeight);
- mRenderer.scale = Math.max(mMinScale, mRenderer.scale);
- }
- }
- }
-
- @Override
- public boolean onScaleBegin(ScaleGestureDetector detector) {
- return true;
- }
-
- @Override
- public boolean onScale(ScaleGestureDetector detector) {
- // Don't need the lock because this will only fire inside of
- // onTouchEvent
- mRenderer.scale *= detector.getScaleFactor();
- mRenderer.scale = Math.max(mMinScale, mRenderer.scale);
- invalidate();
- return true;
- }
-
- @Override
- public void onScaleEnd(ScaleGestureDetector detector) {
- }
-
- public void moveToLeft() {
- if (getWidth() == 0 || getHeight() == 0) {
- final ViewTreeObserver observer = getViewTreeObserver();
- observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
- public void onGlobalLayout() {
- moveToLeft();
- getViewTreeObserver().removeOnGlobalLayoutListener(this);
- }
- });
- }
- final RectF edges = mTempEdges;
- getEdgesHelper(edges);
- final float scale = mRenderer.scale;
- mCenterX += Math.ceil(edges.left / scale);
- updateCenter();
- }
-
- private void updateCenter() {
- mRenderer.centerX = Math.round(mCenterX);
- mRenderer.centerY = Math.round(mCenterY);
- }
-
- public void setTouchEnabled(boolean enabled) {
- mTouchEnabled = enabled;
- }
-
- public void setTouchCallback(TouchCallback cb) {
- mTouchCallback = cb;
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- int action = event.getActionMasked();
- final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP;
- final int skipIndex = pointerUp ? event.getActionIndex() : -1;
-
- // Determine focal point
- float sumX = 0, sumY = 0;
- final int count = event.getPointerCount();
- for (int i = 0; i < count; i++) {
- if (skipIndex == i)
- continue;
- sumX += event.getX(i);
- sumY += event.getY(i);
- }
- final int div = pointerUp ? count - 1 : count;
- float x = sumX / div;
- float y = sumY / div;
-
- if (action == MotionEvent.ACTION_DOWN) {
- mFirstX = x;
- mFirstY = y;
- mTouchDownTime = System.currentTimeMillis();
- if (mTouchCallback != null) {
- mTouchCallback.onTouchDown();
- }
- } else if (action == MotionEvent.ACTION_UP) {
- ViewConfiguration config = ViewConfiguration.get(getContext());
-
- float squaredDist = (mFirstX - x) * (mFirstX - x) + (mFirstY - y) * (mFirstY - y);
- float slop = config.getScaledTouchSlop() * config.getScaledTouchSlop();
- long now = System.currentTimeMillis();
- if (mTouchCallback != null) {
- // only do this if it's a small movement
- if (squaredDist < slop &&
- now < mTouchDownTime + ViewConfiguration.getTapTimeout()) {
- mTouchCallback.onTap();
- }
- mTouchCallback.onTouchUp();
- }
- }
-
- if (!mTouchEnabled) {
- return true;
- }
-
- synchronized (mLock) {
- mScaleGestureDetector.onTouchEvent(event);
- switch (action) {
- case MotionEvent.ACTION_MOVE:
- float[] point = mTempPoint;
- point[0] = (mLastX - x) / mRenderer.scale;
- point[1] = (mLastY - y) / mRenderer.scale;
- mInverseRotateMatrix.mapPoints(point);
- mCenterX += point[0];
- mCenterY += point[1];
- updateCenter();
- invalidate();
- break;
- }
- if (mRenderer.source != null) {
- // Adjust position so that the wallpaper covers the entire area
- // of the screen
- final RectF edges = mTempEdges;
- getEdgesHelper(edges);
- final float scale = mRenderer.scale;
-
- float[] coef = mTempCoef;
- coef[0] = 1;
- coef[1] = 1;
- mRotateMatrix.mapPoints(coef);
- float[] adjustment = mTempAdjustment;
- mTempAdjustment[0] = 0;
- mTempAdjustment[1] = 0;
- if (edges.left > 0) {
- adjustment[0] = edges.left / scale;
- } else if (edges.right < getWidth()) {
- adjustment[0] = (edges.right - getWidth()) / scale;
- }
- if (edges.top > 0) {
- adjustment[1] = FloatMath.ceil(edges.top / scale);
- } else if (edges.bottom < getHeight()) {
- adjustment[1] = (edges.bottom - getHeight()) / scale;
- }
- for (int dim = 0; dim <= 1; dim++) {
- if (coef[dim] > 0) adjustment[dim] = FloatMath.ceil(adjustment[dim]);
- }
-
- mInverseRotateMatrix.mapPoints(adjustment);
- mCenterX += adjustment[0];
- mCenterY += adjustment[1];
- updateCenter();
- }
- }
-
- mLastX = x;
- mLastY = y;
- return true;
- }
-}
diff --git a/src/com/android/launcher3/DeferredHandler.java b/src/com/android/launcher3/DeferredHandler.java
index 92ecf9643..a2d121d63 100644
--- a/src/com/android/launcher3/DeferredHandler.java
+++ b/src/com/android/launcher3/DeferredHandler.java
@@ -21,6 +21,7 @@ import android.os.Looper;
import android.os.Message;
import android.os.MessageQueue;
import android.util.Pair;
+
import java.util.LinkedList;
import java.util.ListIterator;
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index 150d958a8..94a335f5d 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -29,6 +29,7 @@ import android.content.res.Resources;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.TransitionDrawable;
+import android.os.AsyncTask;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewConfiguration;
@@ -95,7 +96,7 @@ public class DeleteDropTarget extends ButtonDropTarget {
}
private boolean isAllAppsApplication(DragSource source, Object info) {
- return (source instanceof AppsCustomizePagedView) && (info instanceof AppInfo);
+ return source.supportsAppInfoDropTarget() && (info instanceof AppInfo);
}
private boolean isAllAppsWidget(DragSource source, Object info) {
if (source instanceof AppsCustomizePagedView) {
@@ -151,12 +152,12 @@ public class DeleteDropTarget extends ButtonDropTarget {
return true;
}
- if (!AppsCustomizePagedView.DISABLE_ALL_APPS &&
+ if (!LauncherAppState.isDisableAllApps() &&
item.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
return true;
}
- if (!AppsCustomizePagedView.DISABLE_ALL_APPS &&
+ if (!LauncherAppState.isDisableAllApps() &&
item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION &&
item instanceof AppInfo) {
AppInfo appInfo = (AppInfo) info;
@@ -165,7 +166,7 @@ public class DeleteDropTarget extends ButtonDropTarget {
if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION &&
item instanceof ShortcutInfo) {
- if (AppsCustomizePagedView.DISABLE_ALL_APPS) {
+ if (LauncherAppState.isDisableAllApps()) {
ShortcutInfo shortcutInfo = (ShortcutInfo) info;
return (shortcutInfo.flags & AppInfo.DOWNLOADED_FLAG) != 0;
} else {
@@ -179,8 +180,9 @@ public class DeleteDropTarget extends ButtonDropTarget {
@Override
public void onDragStart(DragSource source, Object info, int dragAction) {
boolean isVisible = true;
- boolean useUninstallLabel = !AppsCustomizePagedView.DISABLE_ALL_APPS &&
+ boolean useUninstallLabel = !LauncherAppState.isDisableAllApps() &&
isAllAppsApplication(source, info);
+ boolean useDeleteLabel = !useUninstallLabel && source.supportsDeleteDropTarget();
// If we are dragging an application from AppsCustomize, only show the control if we can
// delete the app (it was downloaded), and rename the string to "uninstall" in such a case.
@@ -191,15 +193,17 @@ public class DeleteDropTarget extends ButtonDropTarget {
if (useUninstallLabel) {
setCompoundDrawablesRelativeWithIntrinsicBounds(mUninstallDrawable, null, null, null);
- } else {
+ } else if (useDeleteLabel) {
setCompoundDrawablesRelativeWithIntrinsicBounds(mRemoveDrawable, null, null, null);
+ } else {
+ isVisible = false;
}
mCurrentDrawable = (TransitionDrawable) getCurrentDrawable();
mActive = isVisible;
resetHoverColor();
((ViewGroup) getParent()).setVisibility(isVisible ? View.VISIBLE : View.GONE);
- if (getText().length() > 0) {
+ if (isVisible && getText().length() > 0) {
setText(useUninstallLabel ? R.string.delete_target_uninstall_label
: R.string.delete_target_label);
}
@@ -266,21 +270,10 @@ public class DeleteDropTarget extends ButtonDropTarget {
}
private boolean isUninstallFromWorkspace(DragObject d) {
- if (AppsCustomizePagedView.DISABLE_ALL_APPS && isWorkspaceOrFolderApplication(d)) {
+ if (LauncherAppState.isDisableAllApps() && isWorkspaceOrFolderApplication(d)) {
ShortcutInfo shortcut = (ShortcutInfo) d.dragInfo;
- if (shortcut.intent != null && shortcut.intent.getComponent() != null) {
- Set<String> categories = shortcut.intent.getCategories();
- boolean includesLauncherCategory = false;
- if (categories != null) {
- for (String category : categories) {
- if (category.equals(Intent.CATEGORY_LAUNCHER)) {
- includesLauncherCategory = true;
- break;
- }
- }
- }
- return includesLauncherCategory;
- }
+ // Only allow manifest shortcuts to initiate an un-install.
+ return !InstallShortcutReceiver.isValidShortcutLaunchIntent(shortcut.intent);
}
return false;
}
@@ -340,11 +333,12 @@ public class DeleteDropTarget extends ButtonDropTarget {
if (appWidgetHost != null) {
// Deleting an app widget ID is a void call but writes to disk before returning
// to the caller...
- new Thread("deleteAppWidgetId") {
- public void run() {
+ new AsyncTask<Void, Void, Void>() {
+ public Void doInBackground(Void ... args) {
appWidgetHost.deleteAppWidgetId(launcherAppWidgetInfo.appWidgetId);
+ return null;
}
- }.start();
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
}
}
if (wasWaitingForUninstall && !mWaitingForUninstall) {
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
new file mode 100644
index 000000000..99819caf3
--- /dev/null
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -0,0 +1,886 @@
+/*
+ * Copyright (C) 2008 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.appwidget.AppWidgetHostView;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Paint;
+import android.graphics.Paint.FontMetrics;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.util.DisplayMetrics;
+import android.view.Display;
+import android.view.Gravity;
+import android.view.Surface;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+
+import com.android.launcher3.settings.SettingsProvider;
+
+class DeviceProfileQuery {
+ float widthDps;
+ float heightDps;
+ float value;
+ PointF dimens;
+
+ DeviceProfileQuery(float w, float h, float v) {
+ widthDps = w;
+ heightDps = h;
+ value = v;
+ dimens = new PointF(w, h);
+ }
+}
+
+public class DeviceProfile {
+ public static interface DeviceProfileCallbacks {
+ public void onAvailableSizeChanged(DeviceProfile grid);
+ }
+
+ public final static int GRID_SIZE_MAX = 3;
+ public final static int GRID_SIZE_MIN = 2;
+
+ public enum GridSize {
+ Comfortable(0),
+ Cozy(1),
+ Condensed(2),
+ Custom(3);
+
+ private final int mValue;
+ private GridSize(int value) {
+ mValue = value;
+ }
+
+ public int getValue() {
+ return mValue;
+ }
+
+ public static GridSize getModeForValue(int value) {
+ switch (value) {
+ case 1:
+ return Cozy;
+ case 2:
+ return Condensed;
+ case 3:
+ return Custom;
+ default :
+ return Comfortable;
+ }
+ }
+ }
+
+ String name;
+ float minWidthDps;
+ float minHeightDps;
+ float numRows;
+ float numColumns;
+ int numRowsBase;
+ int numColumnsBase;
+ float numHotseatIcons;
+ private float iconSize;
+ private float iconTextSize;
+ private int iconDrawablePaddingOriginalPx;
+ private float hotseatIconSize;
+
+ boolean isLandscape;
+ boolean isTablet;
+ boolean isLargeTablet;
+ boolean isLayoutRtl;
+ boolean transposeLayoutWithOrientation;
+
+ int desiredWorkspaceLeftRightMarginPx;
+ int edgeMarginPx;
+ Rect defaultWidgetPadding;
+
+ int widthPx;
+ int heightPx;
+ int availableWidthPx;
+ int availableHeightPx;
+ int defaultPageSpacingPx;
+
+ int overviewModeMinIconZoneHeightPx;
+ int overviewModeMaxIconZoneHeightPx;
+ int overviewModeBarItemWidthPx;
+ int overviewModeBarSpacerWidthPx;
+ float overviewModeIconZoneRatio;
+ float overviewModeScaleFactor;
+
+ int iconSizePx;
+ int iconTextSizePx;
+ int iconDrawablePaddingPx;
+ int cellWidthPx;
+ int cellHeightPx;
+ int allAppsIconSizePx;
+ int allAppsIconTextSizePx;
+ int allAppsCellWidthPx;
+ int allAppsCellHeightPx;
+ int allAppsCellPaddingPx;
+ int folderBackgroundOffset;
+ int folderIconSizePx;
+ int folderCellWidthPx;
+ int folderCellHeightPx;
+ int hotseatCellWidthPx;
+ int hotseatCellHeightPx;
+ int hotseatIconSizePx;
+ int hotseatBarHeightPx;
+ int hotseatAllAppsRank;
+ int allAppsNumRows;
+ int allAppsNumCols;
+ int searchBarSpaceWidthPx;
+ int searchBarSpaceMaxWidthPx;
+ int searchBarSpaceHeightPx;
+ int searchBarHeightPx;
+ int pageIndicatorHeightPx;
+
+
+ boolean searchBarVisible;
+
+ float dragViewScale;
+
+ private ArrayList<DeviceProfileCallbacks> mCallbacks = new ArrayList<DeviceProfileCallbacks>();
+
+ DeviceProfile(String n, float w, float h, float r, float c,
+ float is, float its, float hs, float his) {
+ // Ensure that we have an odd number of hotseat items (since we need to place all apps)
+ if (!LauncherAppState.isDisableAllApps() && hs % 2 == 0) {
+ throw new RuntimeException("All Device Profiles must have an odd number of hotseat spaces");
+ }
+
+ name = n;
+ minWidthDps = w;
+ minHeightDps = h;
+ numRows = r;
+ numColumns = c;
+ iconSize = is;
+ iconTextSize = its;
+ numHotseatIcons = hs;
+ hotseatIconSize = his;
+ }
+
+ DeviceProfile(Context context,
+ ArrayList<DeviceProfile> profiles,
+ float minWidth, float minHeight,
+ int wPx, int hPx,
+ int awPx, int ahPx,
+ Resources res) {
+ DisplayMetrics dm = res.getDisplayMetrics();
+ ArrayList<DeviceProfileQuery> points =
+ new ArrayList<DeviceProfileQuery>();
+ transposeLayoutWithOrientation =
+ res.getBoolean(R.bool.hotseat_transpose_layout_with_orientation);
+ minWidthDps = minWidth;
+ minHeightDps = minHeight;
+
+ ComponentName cn = new ComponentName(context.getPackageName(),
+ this.getClass().getName());
+ defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
+ edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
+ desiredWorkspaceLeftRightMarginPx = 2 * edgeMarginPx;
+ pageIndicatorHeightPx =
+ res.getDimensionPixelSize(R.dimen.dynamic_grid_page_indicator_height);
+ defaultPageSpacingPx =
+ res.getDimensionPixelSize(R.dimen.dynamic_grid_workspace_page_spacing);
+ allAppsCellPaddingPx =
+ res.getDimensionPixelSize(R.dimen.dynamic_grid_all_apps_cell_padding);
+ overviewModeMinIconZoneHeightPx =
+ res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_min_icon_zone_height);
+ overviewModeMaxIconZoneHeightPx =
+ res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_max_icon_zone_height);
+ overviewModeBarItemWidthPx =
+ res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_item_width);
+ overviewModeBarSpacerWidthPx =
+ 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;
+
+ // Interpolate the rows
+ for (DeviceProfile p : profiles) {
+ points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numRows));
+ }
+ numRows = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
+ numRowsBase = (int) numRows;
+ int gridResize = SettingsProvider.getIntCustomDefault(context,
+ SettingsProvider.SETTINGS_UI_DYNAMIC_GRID_SIZE, 0);
+ if (GridSize.getModeForValue(gridResize) != GridSize.Custom) {
+ numRows += gridResize;
+ } else {
+ int iTempNumberOfRows = SettingsProvider.getIntCustomDefault(context,
+ SettingsProvider.SETTINGS_UI_HOMESCREEN_ROWS, (int)numRows);
+ if (iTempNumberOfRows > 0) {
+ numRows = iTempNumberOfRows;
+ }
+ }
+ // Interpolate the columns
+ points.clear();
+ for (DeviceProfile p : profiles) {
+ points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numColumns));
+ }
+ numColumns = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
+ numColumnsBase = (int) numColumns;
+ if (GridSize.getModeForValue(gridResize) != GridSize.Custom) {
+ numColumns += gridResize;
+ } else {
+ int iTempNumberOfColumns = SettingsProvider.getIntCustomDefault(context,
+ SettingsProvider.SETTINGS_UI_HOMESCREEN_COLUMNS, (int)numColumns);
+ if (iTempNumberOfColumns > 0) {
+ numColumns = iTempNumberOfColumns;
+ }
+ }
+ // Interpolate the hotseat length
+ points.clear();
+ for (DeviceProfile p : profiles) {
+ points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numHotseatIcons));
+ }
+ numHotseatIcons = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
+ hotseatAllAppsRank = (int) (numHotseatIcons / 2);
+
+ // Interpolate the icon size
+ points.clear();
+ for (DeviceProfile p : profiles) {
+ points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.iconSize));
+ }
+ iconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
+ // AllApps uses the original non-scaled icon size
+ allAppsIconSizePx = DynamicGrid.pxFromDp(iconSize, dm);
+
+ // Interpolate the icon text size
+ points.clear();
+ for (DeviceProfile p : profiles) {
+ points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.iconTextSize));
+ }
+ iconTextSize = invDistWeightedInterpolate(minWidth, minHeight, points);
+ iconDrawablePaddingOriginalPx =
+ res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding);
+ // AllApps uses the original non-scaled icon text size
+ allAppsIconTextSizePx = DynamicGrid.pxFromDp(iconTextSize, dm);
+
+ // Interpolate the hotseat icon size
+ points.clear();
+ for (DeviceProfile p : profiles) {
+ points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.hotseatIconSize));
+ }
+ // Hotseat
+ hotseatIconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
+
+ // Calculate the remaining vars
+ updateFromConfiguration(context, res, wPx, hPx, awPx, ahPx);
+ updateAvailableDimensions(context);
+
+ // Search Bar
+ searchBarVisible = SettingsProvider.getBoolean(context, SettingsProvider.SETTINGS_UI_HOMESCREEN_SEARCH,
+ R.bool.preferences_interface_homescreen_search_default);
+ searchBarSpaceWidthPx = Math.min(searchBarSpaceMaxWidthPx, widthPx);
+ searchBarSpaceHeightPx = 2 * edgeMarginPx + (searchBarVisible ? searchBarHeightPx : 2 * edgeMarginPx);
+ }
+
+ void addCallback(DeviceProfileCallbacks cb) {
+ mCallbacks.add(cb);
+ cb.onAvailableSizeChanged(this);
+ }
+ void removeCallback(DeviceProfileCallbacks cb) {
+ mCallbacks.remove(cb);
+ }
+
+ private int getDeviceOrientation(Context context) {
+ WindowManager windowManager = (WindowManager)
+ context.getSystemService(Context.WINDOW_SERVICE);
+ Resources resources = context.getResources();
+ DisplayMetrics dm = resources.getDisplayMetrics();
+ Configuration config = resources.getConfiguration();
+ int rotation = windowManager.getDefaultDisplay().getRotation();
+
+ boolean isLandscape = (config.orientation == Configuration.ORIENTATION_LANDSCAPE) &&
+ (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180);
+ boolean isRotatedPortrait = (config.orientation == Configuration.ORIENTATION_PORTRAIT) &&
+ (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270);
+ if (isLandscape || isRotatedPortrait) {
+ return CellLayout.LANDSCAPE;
+ } else {
+ return CellLayout.PORTRAIT;
+ }
+ }
+
+ private void updateAvailableDimensions(Context context) {
+ WindowManager windowManager = (WindowManager)
+ context.getSystemService(Context.WINDOW_SERVICE);
+ Display display = windowManager.getDefaultDisplay();
+ Resources resources = context.getResources();
+ DisplayMetrics dm = resources.getDisplayMetrics();
+ Configuration config = resources.getConfiguration();
+
+ // There are three possible configurations that the dynamic grid accounts for, portrait,
+ // landscape with the nav bar at the bottom, and landscape with the nav bar at the side.
+ // To prevent waiting for fitSystemWindows(), we make the observation that in landscape,
+ // the height is the smallest height (either with the nav bar at the bottom or to the
+ // side) and otherwise, the height is simply the largest possible height for a portrait
+ // device.
+ Point size = new Point();
+ Point smallestSize = new Point();
+ Point largestSize = new Point();
+ display.getSize(size);
+ display.getCurrentSizeRange(smallestSize, largestSize);
+ availableWidthPx = size.x;
+ if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ availableHeightPx = smallestSize.y;
+ } else {
+ availableHeightPx = largestSize.y;
+ }
+
+ // Check to see if the icons fit in the new available height. If not, then we need to
+ // shrink the icon size.
+ float scale = 1f;
+ int drawablePadding = iconDrawablePaddingOriginalPx;
+ updateIconSize(1f, drawablePadding, resources, dm);
+ float usedHeight = (cellHeightPx * numRows);
+
+ Rect workspacePadding = getWorkspacePadding();
+ int maxHeight = (availableHeightPx - workspacePadding.top - workspacePadding.bottom);
+ if (usedHeight > maxHeight) {
+ scale = maxHeight / usedHeight;
+ drawablePadding = 0;
+ }
+ updateIconSize(scale, drawablePadding, resources, dm);
+
+ // Make the callbacks
+ for (DeviceProfileCallbacks cb : mCallbacks) {
+ cb.onAvailableSizeChanged(this);
+ }
+ }
+
+ private void updateIconSize(float scale, int drawablePadding, Resources resources,
+ DisplayMetrics dm) {
+ iconSizePx = (int) (DynamicGrid.pxFromDp(iconSize, dm) * scale);
+ iconTextSizePx = (int) (DynamicGrid.pxFromSp(iconTextSize, dm) * scale);
+ iconDrawablePaddingPx = drawablePadding;
+ hotseatIconSizePx = (int) (DynamicGrid.pxFromDp(hotseatIconSize, dm) * scale);
+
+ // Search Bar
+ searchBarSpaceWidthPx = Math.min(searchBarSpaceMaxWidthPx, widthPx);
+ searchBarSpaceMaxWidthPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_max_width);
+ searchBarHeightPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_height);
+ searchBarSpaceHeightPx = searchBarHeightPx + getSearchBarTopOffset();
+
+ // Calculate the actual text height
+ Paint textPaint = new Paint();
+ textPaint.setTextSize(iconTextSizePx);
+ FontMetrics fm = textPaint.getFontMetrics();
+ cellWidthPx = iconSizePx;
+ cellHeightPx = iconSizePx + iconDrawablePaddingPx + (int) Math.ceil(fm.bottom - fm.top);
+ final float scaleDps = resources.getDimensionPixelSize(R.dimen.dragViewScale);
+ dragViewScale = (iconSizePx + scaleDps) / iconSizePx;
+
+ // Hotseat
+ hotseatBarHeightPx = iconSizePx + 4 * edgeMarginPx;
+ hotseatCellWidthPx = iconSizePx;
+ hotseatCellHeightPx = iconSizePx;
+
+ // Folder
+ folderCellWidthPx = cellWidthPx + 3 * edgeMarginPx;
+ folderCellHeightPx = cellHeightPx + edgeMarginPx;
+ folderBackgroundOffset = -edgeMarginPx;
+ folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset;
+
+ // All Apps
+ Rect padding = getWorkspacePadding(isLandscape ?
+ CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
+ int pageIndicatorOffset =
+ resources.getDimensionPixelSize(R.dimen.apps_customize_page_indicator_offset);
+
+ if (isPhone()) {
+ searchBarSpaceWidthPx = Math.min(searchBarSpaceMaxWidthPx, widthPx);
+ } else {
+ searchBarSpaceWidthPx = widthPx - (isLandscape ? 3 : 1) * iconSizePx;
+ }
+
+ allAppsCellWidthPx = allAppsIconSizePx;
+ allAppsCellHeightPx = allAppsIconSizePx + drawablePadding + iconTextSizePx;
+ int maxLongEdgeCellCount =
+ resources.getInteger(R.integer.config_dynamic_grid_max_long_edge_cell_count);
+ int maxShortEdgeCellCount =
+ resources.getInteger(R.integer.config_dynamic_grid_max_short_edge_cell_count);
+ int minEdgeCellCount =
+ resources.getInteger(R.integer.config_dynamic_grid_min_edge_cell_count);
+ int maxRows = (isLandscape ? maxShortEdgeCellCount : maxLongEdgeCellCount);
+ int maxCols = (isLandscape ? maxLongEdgeCellCount : maxShortEdgeCellCount);
+
+ allAppsNumRows = (availableHeightPx - pageIndicatorHeightPx) /
+ (allAppsCellHeightPx + allAppsCellPaddingPx);
+ allAppsNumRows = Math.max(minEdgeCellCount, Math.min(maxRows, allAppsNumRows));
+ allAppsNumCols = (availableWidthPx) /
+ (allAppsCellWidthPx + allAppsCellPaddingPx);
+ allAppsNumCols = Math.max(minEdgeCellCount, Math.min(maxCols, allAppsNumCols));
+ }
+
+ void updateFromConfiguration(Context context, Resources resources, int wPx, int hPx,
+ int awPx, int ahPx) {
+ Configuration configuration = resources.getConfiguration();
+ isLandscape = (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE);
+ isTablet = resources.getBoolean(R.bool.is_tablet);
+ isLargeTablet = resources.getBoolean(R.bool.is_large_tablet);
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ isLayoutRtl = (configuration.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
+ } else {
+ isLayoutRtl = false;
+ }
+ widthPx = wPx;
+ heightPx = hPx;
+ availableWidthPx = awPx;
+ availableHeightPx = ahPx;
+
+ updateAvailableDimensions(context);
+ }
+
+ private float dist(PointF p0, PointF p1) {
+ return (float) Math.sqrt((p1.x - p0.x)*(p1.x-p0.x) +
+ (p1.y-p0.y)*(p1.y-p0.y));
+ }
+
+ private float weight(PointF a, PointF b,
+ float pow) {
+ float d = dist(a, b);
+ if (d == 0f) {
+ return Float.POSITIVE_INFINITY;
+ }
+ return (float) (1f / Math.pow(d, pow));
+ }
+
+ private float invDistWeightedInterpolate(float width, float height,
+ ArrayList<DeviceProfileQuery> points) {
+ float sum = 0;
+ float weights = 0;
+ float pow = 5;
+ float kNearestNeighbors = 3;
+ final PointF xy = new PointF(width, height);
+
+ ArrayList<DeviceProfileQuery> pointsByNearness = points;
+ Collections.sort(pointsByNearness, new Comparator<DeviceProfileQuery>() {
+ public int compare(DeviceProfileQuery a, DeviceProfileQuery b) {
+ return (int) (dist(xy, a.dimens) - dist(xy, b.dimens));
+ }
+ });
+
+ for (int i = 0; i < pointsByNearness.size(); ++i) {
+ DeviceProfileQuery p = pointsByNearness.get(i);
+ if (i < kNearestNeighbors) {
+ float w = weight(xy, p.dimens, pow);
+ if (w == Float.POSITIVE_INFINITY) {
+ return p.value;
+ }
+ weights += w;
+ }
+ }
+
+ for (int i = 0; i < pointsByNearness.size(); ++i) {
+ DeviceProfileQuery p = pointsByNearness.get(i);
+ if (i < kNearestNeighbors) {
+ float w = weight(xy, p.dimens, pow);
+ sum += w * p.value / weights;
+ }
+ }
+
+ return sum;
+ }
+
+ /** Returns the search bar top offset */
+ int getSearchBarTopOffset() {
+ if (isTablet() && !isVerticalBarLayout()) {
+ return searchBarVisible ? 4 * edgeMarginPx : 0;
+ } else {
+ return searchBarVisible ? 2 * edgeMarginPx : 0;
+ }
+ }
+
+ /** Returns the search bar bounds in the current orientation */
+ Rect getSearchBarBounds() {
+ return getSearchBarBounds(isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
+ }
+ /** Returns the search bar bounds in the specified orientation */
+ Rect getSearchBarBounds(int orientation) {
+ Rect bounds = new Rect();
+ if (orientation == CellLayout.LANDSCAPE &&
+ transposeLayoutWithOrientation) {
+ if (isLayoutRtl) {
+ bounds.set(availableWidthPx - searchBarSpaceHeightPx, edgeMarginPx,
+ availableWidthPx, availableHeightPx - edgeMarginPx);
+ } else {
+ bounds.set(0, edgeMarginPx, searchBarSpaceHeightPx,
+ availableHeightPx - edgeMarginPx);
+ }
+ } else {
+ if (isTablet()) {
+ // Pad the left and right of the workspace to ensure consistent spacing
+ // between all icons
+ int width = (orientation == CellLayout.LANDSCAPE)
+ ? Math.max(widthPx, heightPx)
+ : Math.min(widthPx, heightPx);
+ // XXX: If the icon size changes across orientations, we will have to take
+ // that into account here too.
+ int gap = (int) ((width - 2 * edgeMarginPx -
+ (numColumns * cellWidthPx)) / (2 * (numColumns + 1)));
+ bounds.set(edgeMarginPx + gap, getSearchBarTopOffset(),
+ availableWidthPx - (edgeMarginPx + gap),
+ searchBarVisible ? searchBarSpaceHeightPx : edgeMarginPx);
+ } else {
+ bounds.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
+ getSearchBarTopOffset(),
+ availableWidthPx - (desiredWorkspaceLeftRightMarginPx -
+ defaultWidgetPadding.right), searchBarVisible ? searchBarSpaceHeightPx : edgeMarginPx);
+ }
+ }
+ return bounds;
+ }
+
+ /** Returns the bounds of the workspace page indicators. */
+ Rect getWorkspacePageIndicatorBounds(Rect insets) {
+ Rect workspacePadding = getWorkspacePadding();
+ if (isLandscape && transposeLayoutWithOrientation) {
+ if (isLayoutRtl) {
+ return new Rect(workspacePadding.left, workspacePadding.top,
+ workspacePadding.left + pageIndicatorHeightPx,
+ heightPx - workspacePadding.bottom - insets.bottom);
+ } else {
+ int pageIndicatorLeft = widthPx - workspacePadding.right;
+ return new Rect(pageIndicatorLeft, workspacePadding.top,
+ pageIndicatorLeft + pageIndicatorHeightPx,
+ heightPx - workspacePadding.bottom - insets.bottom);
+ }
+ } else {
+ int pageIndicatorTop = heightPx - insets.bottom - workspacePadding.bottom;
+ return new Rect(workspacePadding.left, pageIndicatorTop,
+ widthPx - workspacePadding.right, pageIndicatorTop + pageIndicatorHeightPx);
+ }
+ }
+
+ /** Returns the workspace padding in the specified orientation */
+ Rect getWorkspacePadding() {
+ return getWorkspacePadding(isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
+ }
+ Rect getWorkspacePadding(int orientation) {
+ Rect searchBarBounds = getSearchBarBounds(orientation);
+ Rect padding = new Rect();
+ if (orientation == CellLayout.LANDSCAPE &&
+ transposeLayoutWithOrientation) {
+ // Pad the left and right of the workspace with search/hotseat bar sizes
+ if (isLayoutRtl) {
+ padding.set(hotseatBarHeightPx, edgeMarginPx,
+ searchBarBounds.width(), edgeMarginPx);
+ } else {
+ padding.set(searchBarBounds.width(), edgeMarginPx,
+ hotseatBarHeightPx, edgeMarginPx);
+ }
+ } else {
+ if (isTablet()) {
+ // Pad the left and right of the workspace to ensure consistent spacing
+ // between all icons
+ float gapScale = 1f + (dragViewScale - 1f) / 2f;
+ int width = (orientation == CellLayout.LANDSCAPE)
+ ? Math.max(widthPx, heightPx)
+ : Math.min(widthPx, heightPx);
+ int height = (orientation != CellLayout.LANDSCAPE)
+ ? Math.max(widthPx, heightPx)
+ : Math.min(widthPx, heightPx);
+ int paddingTop = searchBarBounds.bottom;
+ int paddingBottom = hotseatBarHeightPx + pageIndicatorHeightPx;
+ int availableWidth = Math.max(0, width - (int) ((numColumns * cellWidthPx) +
+ (numColumns * gapScale * cellWidthPx)));
+ int availableHeight = Math.max(0, height - paddingTop - paddingBottom
+ - (int) (2 * numRows * cellHeightPx));
+ padding.set(availableWidth / 2, paddingTop + availableHeight / 2,
+ availableWidth / 2, paddingBottom + availableHeight / 2);
+ } else {
+ // Pad the top and bottom of the workspace with search/hotseat bar sizes
+ padding.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
+ searchBarBounds.bottom,
+ desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.right,
+ hotseatBarHeightPx + pageIndicatorHeightPx);
+ }
+ }
+ return padding;
+ }
+
+ int getWorkspacePageSpacing(int orientation) {
+ if ((orientation == CellLayout.LANDSCAPE &&
+ transposeLayoutWithOrientation) || isLargeTablet()) {
+ // In landscape mode the page spacing is set to the default.
+ return defaultPageSpacingPx;
+ } else {
+ // In portrait, we want the pages spaced such that there is no
+ // overhang of the previous / next page into the current page viewport.
+ // We assume symmetrical padding in portrait mode.
+ return Math.max(defaultPageSpacingPx, 2 * getWorkspacePadding().left);
+ }
+ }
+
+ Rect getOverviewModeButtonBarRect() {
+ int zoneHeight = (int) (overviewModeIconZoneRatio * availableHeightPx);
+ zoneHeight = Math.min(overviewModeMaxIconZoneHeightPx,
+ Math.max(overviewModeMinIconZoneHeightPx, zoneHeight));
+ return new Rect(0, availableHeightPx - zoneHeight, 0, availableHeightPx);
+ }
+
+ float getOverviewModeScale() {
+ Rect workspacePadding = getWorkspacePadding();
+ Rect overviewBar = getOverviewModeButtonBarRect();
+ int pageSpace = availableHeightPx - workspacePadding.top - workspacePadding.bottom;
+ return (overviewModeScaleFactor * (pageSpace - overviewBar.height())) / pageSpace;
+ }
+
+ // The rect returned will be extended to below the system ui that covers the workspace
+ Rect getHotseatRect() {
+ if (isVerticalBarLayout()) {
+ return new Rect(availableWidthPx - hotseatBarHeightPx, 0,
+ Integer.MAX_VALUE, availableHeightPx);
+ } else {
+ return new Rect(0, availableHeightPx - hotseatBarHeightPx,
+ availableWidthPx, Integer.MAX_VALUE);
+ }
+ }
+
+ int calculateCellWidth(int width, int countX) {
+ return width / countX;
+ }
+ int calculateCellHeight(int height, int countY) {
+ return height / countY;
+ }
+
+ boolean isPhone() {
+ return !isTablet && !isLargeTablet;
+ }
+ boolean isTablet() {
+ return isTablet;
+ }
+ boolean isLargeTablet() {
+ return isLargeTablet;
+ }
+
+ boolean isVerticalBarLayout() {
+ return isLandscape && transposeLayoutWithOrientation;
+ }
+
+ boolean shouldFadeAdjacentWorkspaceScreens() {
+ return isVerticalBarLayout() || isLargeTablet();
+ }
+
+ int getVisibleChildCount(ViewGroup parent) {
+ int visibleChildren = 0;
+ for (int i = 0; i < parent.getChildCount(); i++) {
+ if (parent.getChildAt(i).getVisibility() != View.GONE) {
+ visibleChildren++;
+ }
+ }
+ return visibleChildren;
+ }
+
+ int calculateOverviewModeWidth(int visibleChildCount) {
+ return visibleChildCount * overviewModeBarItemWidthPx +
+ (visibleChildCount-1) * overviewModeBarSpacerWidthPx;
+ }
+
+ public void layout(Launcher launcher) {
+ // Update search bar for live settings
+ searchBarVisible = SettingsProvider.getBoolean(launcher, SettingsProvider.SETTINGS_UI_HOMESCREEN_SEARCH,
+ R.bool.preferences_interface_homescreen_search_default);
+ searchBarSpaceHeightPx = 2 * edgeMarginPx + (searchBarVisible ? searchBarHeightPx : 2 * edgeMarginPx);
+ FrameLayout.LayoutParams lp;
+ Resources res = launcher.getResources();
+ boolean hasVerticalBarLayout = isVerticalBarLayout();
+
+ // Layout the search bar space
+ View searchBar = launcher.getSearchBar();
+ lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams();
+ if (hasVerticalBarLayout) {
+ // Vertical search bar space
+ lp.gravity = Gravity.TOP | Gravity.LEFT;
+ lp.width = searchBarSpaceHeightPx;
+ lp.height = LayoutParams.WRAP_CONTENT;
+ searchBar.setPadding(
+ 0, 2 * edgeMarginPx, 0,
+ 2 * edgeMarginPx);
+
+ searchBar.setVisibility(searchBarVisible ? View.VISIBLE : View.GONE);
+ LinearLayout targets = (LinearLayout) searchBar.findViewById(R.id.drag_target_bar);
+ targets.setOrientation(LinearLayout.VERTICAL);
+ } else {
+ // Horizontal search bar space
+ lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
+ lp.width = searchBarSpaceWidthPx;
+ lp.height = searchBarSpaceHeightPx;
+ searchBar.setPadding(
+ 2 * edgeMarginPx,
+ getSearchBarTopOffset(),
+ 2 * edgeMarginPx, 0);
+ }
+ searchBar.setLayoutParams(lp);
+
+ // Layout the drop target icons
+ LinearLayout dropTargetBar = (LinearLayout) launcher.getSearchBar().getDropTargetBar();
+ if (hasVerticalBarLayout) {
+ dropTargetBar.setOrientation(LinearLayout.VERTICAL);
+ } else {
+ dropTargetBar.setOrientation(LinearLayout.HORIZONTAL);
+ }
+
+ // Layout the search bar
+ View qsbBar = launcher.getQsbBar();
+ qsbBar.setVisibility(searchBarVisible ? View.VISIBLE : View.GONE);
+ LayoutParams vglp = qsbBar.getLayoutParams();
+ vglp.width = LayoutParams.MATCH_PARENT;
+ vglp.height = LayoutParams.MATCH_PARENT;
+ qsbBar.setLayoutParams(vglp);
+
+ // Layout the voice proxy
+ View voiceButtonProxy = launcher.findViewById(R.id.voice_button_proxy);
+ if (voiceButtonProxy != null) {
+ if (hasVerticalBarLayout) {
+ // TODO: MOVE THIS INTO SEARCH BAR MEASURE
+ } else {
+ lp = (FrameLayout.LayoutParams) voiceButtonProxy.getLayoutParams();
+ lp.gravity = Gravity.TOP | Gravity.END;
+ lp.width = (widthPx - searchBarSpaceWidthPx) / 2 +
+ 2 * iconSizePx;
+ lp.height = searchBarSpaceHeightPx;
+ }
+ }
+
+ // Layout the workspace
+ PagedView workspace = (PagedView) launcher.findViewById(R.id.workspace);
+ lp = (FrameLayout.LayoutParams) workspace.getLayoutParams();
+ lp.gravity = Gravity.CENTER;
+ int orientation = isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT;
+ Rect padding = getWorkspacePadding(orientation);
+ workspace.setLayoutParams(lp);
+ workspace.setPadding(padding.left, padding.top, padding.right, padding.bottom);
+ workspace.setPageSpacing(getWorkspacePageSpacing(orientation));
+
+ // Layout the hotseat
+ View hotseat = launcher.findViewById(R.id.hotseat);
+ lp = (FrameLayout.LayoutParams) hotseat.getLayoutParams();
+ if (hasVerticalBarLayout) {
+ // Vertical hotseat
+ lp.gravity = Gravity.END;
+ lp.width = hotseatBarHeightPx;
+ lp.height = LayoutParams.MATCH_PARENT;
+ hotseat.findViewById(R.id.layout).setPadding(0, 2 * edgeMarginPx, 0, 2 * edgeMarginPx);
+ } else if (isTablet()) {
+ // Pad the hotseat with the workspace padding calculated above
+ lp.gravity = Gravity.BOTTOM;
+ lp.width = LayoutParams.MATCH_PARENT;
+ lp.height = hotseatBarHeightPx;
+ hotseat.setPadding(edgeMarginPx + padding.left, 0,
+ edgeMarginPx + padding.right,
+ 2 * edgeMarginPx);
+ } else {
+ // For phones, layout the hotseat without any bottom margin
+ // to ensure that we have space for the folders
+ lp.gravity = Gravity.BOTTOM;
+ lp.width = LayoutParams.MATCH_PARENT;
+ lp.height = hotseatBarHeightPx;
+ hotseat.findViewById(R.id.layout).setPadding(2 * edgeMarginPx, 0,
+ 2 * edgeMarginPx, 0);
+ }
+ hotseat.setLayoutParams(lp);
+
+ // Layout the page indicators
+ View pageIndicator = launcher.findViewById(R.id.page_indicator);
+ if (pageIndicator != null) {
+ if (hasVerticalBarLayout) {
+ // Hide the page indicators when we have vertical search/hotseat
+ pageIndicator.setVisibility(View.GONE);
+ } else {
+ // Put the page indicators above the hotseat
+ lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams();
+ lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
+ lp.width = LayoutParams.WRAP_CONTENT;
+ lp.height = LayoutParams.WRAP_CONTENT;
+ lp.bottomMargin = Math.max(hotseatBarHeightPx, lp.bottomMargin);
+ pageIndicator.setLayoutParams(lp);
+ }
+ }
+
+ // Layout the apps customize
+ View appsCustomize = launcher.findViewById(R.id.apps_customize_pane_content);
+ lp = (FrameLayout.LayoutParams) appsCustomize.getLayoutParams();
+ lp.gravity = Gravity.CENTER;
+ appsCustomize.setLayoutParams(lp);
+
+ // Layout AllApps
+ AppsCustomizeLayout host = (AppsCustomizeLayout)
+ launcher.findViewById(R.id.apps_customize_pane);
+ if (host != null) {
+ // Center the all apps page indicator
+ int pageIndicatorHeight = (int) (pageIndicatorHeightPx * Math.min(1f,
+ (allAppsIconSizePx / DynamicGrid.DEFAULT_ICON_SIZE_PX)));
+ pageIndicator = host.findViewById(R.id.apps_customize_page_indicator);
+ if (pageIndicator != null) {
+ lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams();
+ lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
+ lp.width = LayoutParams.WRAP_CONTENT;
+ lp.height = pageIndicatorHeight;
+ pageIndicator.setLayoutParams(lp);
+ }
+
+ AppsCustomizePagedView pagedView = (AppsCustomizePagedView)
+ host.findViewById(R.id.apps_customize_pane_content);
+ padding = new Rect();
+ if (pagedView != null) {
+ // Constrain the dimensions of all apps so that it does not span the full width
+ int paddingLR = (availableWidthPx - (allAppsCellWidthPx * allAppsNumCols)) /
+ (2 * (allAppsNumCols + 1));
+ int paddingTB = (availableHeightPx - (allAppsCellHeightPx * allAppsNumRows)) /
+ (2 * (allAppsNumRows + 1));
+ paddingLR = Math.min(paddingLR, (int)((paddingLR + paddingTB) * 0.75f));
+ paddingTB = Math.min(paddingTB, (int)((paddingLR + paddingTB) * 0.75f));
+ int maxAllAppsWidth = (allAppsNumCols * (allAppsCellWidthPx + 2 * paddingLR));
+ int gridPaddingLR = (availableWidthPx - maxAllAppsWidth) / 2;
+ // Only adjust the side paddings on landscape phones, or tablets
+ if ((isTablet() || isLandscape) && gridPaddingLR > (allAppsCellWidthPx / 4)) {
+ padding.left = padding.right = gridPaddingLR;
+ }
+ // The icons are centered, so we can't just offset by the page indicator height
+ // because the empty space will actually be pageIndicatorHeight + paddingTB
+ padding.bottom = Math.max(0, pageIndicatorHeight - paddingTB);
+ pagedView.setAllAppsPadding(padding);
+ pagedView.setWidgetsPageIndicatorPadding(pageIndicatorHeight);
+ }
+ }
+
+ // Layout the Overview Mode
+// ViewGroup overviewMode = launcher.getOverviewPanel();
+// if (overviewMode != null) {
+// Rect r = getOverviewModeButtonBarRect();
+// lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams();
+// lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
+// lp.width = Math.min(availableWidthPx,
+// calculateOverviewModeWidth(getVisibleChildCount(overviewMode)));
+// lp.height = r.height();
+// overviewMode.setLayoutParams(lp);
+// }
+ }
+}
diff --git a/src/com/android/launcher3/DragController.java b/src/com/android/launcher3/DragController.java
index 5b5c35c5a..4c3ea2a0a 100644
--- a/src/com/android/launcher3/DragController.java
+++ b/src/com/android/launcher3/DragController.java
@@ -16,6 +16,7 @@
package com.android.launcher3;
+import android.content.ComponentName;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
@@ -25,16 +26,9 @@ import android.graphics.Rect;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;
-import android.view.HapticFeedbackConstants;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.ViewConfiguration;
+import android.view.*;
import android.view.inputmethod.InputMethodManager;
-import com.android.launcher3.R;
-
import java.util.ArrayList;
/**
@@ -203,7 +197,7 @@ public class DragController {
* @param dragRegion Coordinates within the bitmap b for the position of item being dragged.
* Makes dragging feel more precise, e.g. you can clip out a transparent border
*/
- public void startDrag(Bitmap b, int dragLayerX, int dragLayerY,
+ public DragView startDrag(Bitmap b, int dragLayerX, int dragLayerY,
DragSource source, Object dragInfo, int dragAction, Point dragOffset, Rect dragRegion,
float initialDragViewScale) {
if (PROFILE_DRAWING_DURING_DRAG) {
@@ -250,6 +244,7 @@ public class DragController {
mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
dragView.show(mMotionDownX, mMotionDownY);
handleMoveEvent(mMotionDownX, mMotionDownY);
+ return dragView;
}
/**
@@ -323,7 +318,7 @@ public class DragController {
}
endDrag();
}
- public void onAppsRemoved(ArrayList<AppInfo> appInfos, Context context) {
+ public void onAppsRemoved(final ArrayList<String> packageNames, ArrayList<AppInfo> appInfos) {
// Cancel the current drag if we are removing an app that we are dragging
if (mDragObject != null) {
Object rawDragInfo = mDragObject.dragInfo;
@@ -332,9 +327,10 @@ public class DragController {
for (AppInfo info : appInfos) {
// Added null checks to prevent NPE we've seen in the wild
if (dragInfo != null &&
- dragInfo.intent != null) {
- boolean isSameComponent =
- dragInfo.intent.getComponent().equals(info.componentName);
+ dragInfo.intent != null && info != null) {
+ ComponentName cn = dragInfo.intent.getComponent();
+ boolean isSameComponent = cn.equals(info.componentName) ||
+ packageNames.contains(cn.getPackageName());
if (isSameComponent) {
cancelDrag();
return;
diff --git a/src/com/android/launcher3/DragLayer.java b/src/com/android/launcher3/DragLayer.java
index 401f4ed52..3ff4293af 100644
--- a/src/com/android/launcher3/DragLayer.java
+++ b/src/com/android/launcher3/DragLayer.java
@@ -24,11 +24,13 @@ import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
-import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
-import android.view.*;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.animation.DecelerateInterpolator;
@@ -63,8 +65,6 @@ public class DragLayer extends FrameLayout implements ViewGroup.OnHierarchyChang
private boolean mHoverPointClosesFolder = false;
private Rect mHitRect = new Rect();
- private int mWorkspaceIndex = -1;
- private int mQsbIndex = -1;
public static final int ANIMATION_END_DISAPPEAR = 0;
public static final int ANIMATION_END_FADE_OUT = 1;
public static final int ANIMATION_END_REMAIN_VISIBLE = 2;
@@ -73,6 +73,8 @@ public class DragLayer extends FrameLayout implements ViewGroup.OnHierarchyChang
private final Rect mInsets = new Rect();
+ private int mDragViewIndex;
+
/**
* Used to create a new DragLayer from XML.
*
@@ -106,21 +108,34 @@ public class DragLayer extends FrameLayout implements ViewGroup.OnHierarchyChang
final int n = getChildCount();
for (int i = 0; i < n; i++) {
final View child = getChildAt(i);
- final FrameLayout.LayoutParams flp = (FrameLayout.LayoutParams) child.getLayoutParams();
- if (child instanceof Insettable) {
- ((Insettable)child).setInsets(insets);
- } else {
- flp.topMargin += (insets.top - mInsets.top);
- flp.leftMargin += (insets.left - mInsets.left);
- flp.rightMargin += (insets.right - mInsets.right);
- flp.bottomMargin += (insets.bottom - mInsets.bottom);
+ if (child.getId() == R.id.overview_panel) {
+ continue;
}
- child.setLayoutParams(flp);
+ setInsets(child, insets, mInsets);
}
mInsets.set(insets);
return true; // I'll take it from here
}
+ @Override
+ public void addView(View child, int index, android.view.ViewGroup.LayoutParams params) {
+ super.addView(child, index, params);
+ setInsets(child, mInsets, new Rect());
+ }
+
+ private void setInsets(View child, Rect newInsets, Rect oldInsets) {
+ final FrameLayout.LayoutParams flp = (FrameLayout.LayoutParams) child.getLayoutParams();
+ if (child instanceof Insettable) {
+ ((Insettable) child).setInsets(newInsets);
+ } else {
+ flp.topMargin += (newInsets.top - oldInsets.top);
+ flp.leftMargin += (newInsets.left - oldInsets.left);
+ flp.rightMargin += (newInsets.right - oldInsets.right);
+ flp.bottomMargin += (newInsets.bottom - oldInsets.bottom);
+ }
+ child.setLayoutParams(flp);
+ }
+
private boolean isEventOverFolderTextRegion(Folder folder, MotionEvent ev) {
getDescendantRectRelativeToSelf(folder.getEditTextRegion(), mHitRect);
if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) {
@@ -156,7 +171,8 @@ public class DragLayer extends FrameLayout implements ViewGroup.OnHierarchyChang
}
Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
- if (currentFolder != null && !mLauncher.isFolderClingVisible() && intercept) {
+ if (currentFolder != null && !mLauncher.getLauncherClings().isFolderClingVisible() &&
+ intercept) {
if (currentFolder.isEditingName()) {
if (!isEventOverFolderTextRegion(currentFolder, ev)) {
currentFolder.dismissEditingName();
@@ -212,22 +228,19 @@ public class DragLayer extends FrameLayout implements ViewGroup.OnHierarchyChang
sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName());
mHoverPointClosesFolder = true;
return true;
- } else if (isOverFolder) {
- mHoverPointClosesFolder = false;
- } else {
- return true;
}
+ mHoverPointClosesFolder = false;
+ break;
case MotionEvent.ACTION_HOVER_MOVE:
isOverFolder = isEventOverFolder(currentFolder, ev);
if (!isOverFolder && !mHoverPointClosesFolder) {
sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName());
mHoverPointClosesFolder = true;
return true;
- } else if (isOverFolder) {
- mHoverPointClosesFolder = false;
- } else {
+ } else if (!isOverFolder) {
return true;
}
+ mHoverPointClosesFolder = false;
}
}
}
@@ -480,7 +493,7 @@ public class DragLayer extends FrameLayout implements ViewGroup.OnHierarchyChang
}
public void animateViewIntoPosition(DragView dragView, final View child) {
- animateViewIntoPosition(dragView, child, null);
+ animateViewIntoPosition(dragView, child, null, null);
}
public void animateViewIntoPosition(DragView dragView, final int[] pos, float alpha,
@@ -496,8 +509,8 @@ public class DragLayer extends FrameLayout implements ViewGroup.OnHierarchyChang
}
public void animateViewIntoPosition(DragView dragView, final View child,
- final Runnable onFinishAnimationRunnable) {
- animateViewIntoPosition(dragView, child, -1, onFinishAnimationRunnable, null);
+ final Runnable onFinishAnimationRunnable, View anchorView) {
+ animateViewIntoPosition(dragView, child, -1, onFinishAnimationRunnable, anchorView);
}
public void animateViewIntoPosition(DragView dragView, final View child, int duration,
@@ -522,14 +535,18 @@ public class DragLayer extends FrameLayout implements ViewGroup.OnHierarchyChang
scale *= childScale;
int toX = coord[0];
int toY = coord[1];
+ float toScale = scale;
if (child instanceof TextView) {
TextView tv = (TextView) child;
+ // Account for the source scale of the icon (ie. from AllApps to Workspace, in which
+ // the workspace may have smaller icon bounds).
+ toScale = scale / dragView.getIntrinsicIconScaleFactor();
// The child may be scaled (always about the center of the view) so to account for it,
// we have to offset the position by the scaled size. Once we do that, we can center
// the drag view about the scaled child view.
- toY += Math.round(scale * tv.getPaddingTop());
- toY -= dragView.getMeasuredHeight() * (1 - scale) / 2;
+ toY += Math.round(toScale * tv.getPaddingTop());
+ toY -= dragView.getMeasuredHeight() * (1 - toScale) / 2;
toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
} else if (child instanceof FolderIcon) {
// Account for holographic blur padding on the drag view
@@ -555,7 +572,7 @@ public class DragLayer extends FrameLayout implements ViewGroup.OnHierarchyChang
}
}
};
- animateViewIntoPosition(dragView, fromX, fromY, toX, toY, 1, 1, 1, scale, scale,
+ animateViewIntoPosition(dragView, fromX, fromY, toX, toY, 1, 1, 1, toScale, toScale,
onCompleteRunnable, ANIMATION_END_DISAPPEAR, duration, anchorView);
}
@@ -645,8 +662,10 @@ public class DragLayer extends FrameLayout implements ViewGroup.OnHierarchyChang
int x = (int) (fromLeft + Math.round(((to.left - fromLeft) * motionPercent)));
int y = (int) (fromTop + Math.round(((to.top - fromTop) * motionPercent)));
- int xPos = x - mDropView.getScrollX() + (mAnchorView != null
- ? (mAnchorViewInitialScrollX - mAnchorView.getScrollX()) : 0);
+ int anchorAdjust = mAnchorView == null ? 0 : (int) (mAnchorView.getScaleX() *
+ (mAnchorViewInitialScrollX - mAnchorView.getScrollX()));
+
+ int xPos = x - mDropView.getScrollX() + anchorAdjust;
int yPos = y - mDropView.getScrollY();
mDropView.setTranslationX(xPos);
@@ -755,31 +774,26 @@ public class DragLayer extends FrameLayout implements ViewGroup.OnHierarchyChang
}
private void updateChildIndices() {
- if (mLauncher != null) {
- mWorkspaceIndex = indexOfChild(mLauncher.getWorkspace());
- mQsbIndex = indexOfChild(mLauncher.getSearchBar());
+ mDragViewIndex = -1;
+ int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ if (getChildAt(i) instanceof DragView) {
+ mDragViewIndex = i;
+ }
}
}
@Override
protected int getChildDrawingOrder(int childCount, int i) {
- // TODO: We have turned off this custom drawing order because it now effects touch
- // dispatch order. We need to sort that issue out and then decide how to go about this.
- if (true || LauncherAppState.isScreenLandscape(getContext()) ||
- mWorkspaceIndex == -1 || mQsbIndex == -1 ||
- mLauncher.getWorkspace().isDrawingBackgroundGradient()) {
+ if (mDragViewIndex == -1) {
return i;
- }
-
- // This ensures that the workspace is drawn above the hotseat and qsb,
- // except when the workspace is drawing a background gradient, in which
- // case we want the workspace to stay behind these elements.
- if (i == mQsbIndex) {
- return mWorkspaceIndex;
- } else if (i == mWorkspaceIndex) {
- return mQsbIndex;
- } else {
+ } else if (i == mDragViewIndex) {
+ return getChildCount()-1;
+ } else if (i < mDragViewIndex) {
return i;
+ } else {
+ // i > mDragViewIndex
+ return i-1;
}
}
@@ -800,7 +814,6 @@ public class DragLayer extends FrameLayout implements ViewGroup.OnHierarchyChang
/**
* Note: this is a reimplementation of View.isLayoutRtl() since that is currently hidden api.
*/
- @Override
public boolean isLayoutRtl() {
return (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
}
diff --git a/src/com/android/launcher3/DragSource.java b/src/com/android/launcher3/DragSource.java
index 2ef99ae08..7369eeac2 100644
--- a/src/com/android/launcher3/DragSource.java
+++ b/src/com/android/launcher3/DragSource.java
@@ -31,6 +31,22 @@ public interface DragSource {
boolean supportsFlingToDelete();
/**
+ * @return whether items dragged from this source supports 'App Info'
+ */
+ boolean supportsAppInfoDropTarget();
+
+ /**
+ * @return whether items dragged from this source supports 'Delete' drop target (e.g. to remove
+ * a shortcut.
+ */
+ boolean supportsDeleteDropTarget();
+
+ /*
+ * @return the scale of the icons over the workspace icon size
+ */
+ float getIntrinsicIconScaleFactor();
+
+ /**
* A callback specifically made back to the source after an item from this source has been flung
* to be deleted on a DropTarget. In such a situation, this method will be called after
* onDropCompleted, and more importantly, after the fling animation has completed.
diff --git a/src/com/android/launcher3/DragView.java b/src/com/android/launcher3/DragView.java
index 686cf62ff..ea34e46f9 100644
--- a/src/com/android/launcher3/DragView.java
+++ b/src/com/android/launcher3/DragView.java
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-
package com.android.launcher3;
import android.animation.ValueAnimator;
@@ -30,8 +29,6 @@ import android.graphics.Rect;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
-import com.android.launcher3.R;
-
public class DragView extends View {
private static float sDragAlpha = 1f;
@@ -51,6 +48,9 @@ public class DragView extends View {
private float mOffsetX = 0.0f;
private 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;
/**
* Construct the drag view.
@@ -120,6 +120,15 @@ public class DragView extends View {
mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
}
+ /** Sets the scale of the view over the normal workspace icon size. */
+ public void setIntrinsicIconScaleFactor(float scale) {
+ mIntrinsicIconScale = scale;
+ }
+
+ public float getIntrinsicIconScaleFactor() {
+ return mIntrinsicIconScale;
+ }
+
public float getOffsetY() {
return mOffsetY;
}
diff --git a/src/com/android/launcher3/DrawableStateProxyView.java b/src/com/android/launcher3/DrawableStateProxyView.java
index 0758de1f7..c83659ad5 100644
--- a/src/com/android/launcher3/DrawableStateProxyView.java
+++ b/src/com/android/launcher3/DrawableStateProxyView.java
@@ -23,8 +23,6 @@ import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
-import com.android.launcher3.R;
-
public class DrawableStateProxyView extends LinearLayout {
private View mView;
diff --git a/src/com/android/launcher3/DynamicGrid.java b/src/com/android/launcher3/DynamicGrid.java
index 6457fdc31..9be377e8e 100644
--- a/src/com/android/launcher3/DynamicGrid.java
+++ b/src/com/android/launcher3/DynamicGrid.java
@@ -16,495 +16,14 @@
package com.android.launcher3;
-import android.appwidget.AppWidgetHostView;
-import android.content.ComponentName;
import android.content.Context;
-import android.content.res.Configuration;
import android.content.res.Resources;
-import android.graphics.Paint;
-import android.graphics.Paint.FontMetrics;
-import android.graphics.PointF;
-import android.graphics.Rect;
import android.util.DisplayMetrics;
import android.util.TypedValue;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup.LayoutParams;
-import android.widget.FrameLayout;
-import android.widget.LinearLayout;
import com.android.launcher3.settings.SettingsProvider;
import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-
-
-class DeviceProfileQuery {
- float widthDps;
- float heightDps;
- float value;
- PointF dimens;
-
- DeviceProfileQuery(float w, float h, float v) {
- widthDps = w;
- heightDps = h;
- value = v;
- dimens = new PointF(w, h);
- }
-}
-
-class DeviceProfile {
- String name;
- float minWidthDps;
- float minHeightDps;
- float numRows;
- float numColumns;
- float iconSize;
- float iconTextSize;
- float numHotseatIcons;
- float hotseatIconSize;
-
- boolean isLandscape;
- boolean isTablet;
- boolean isLargeTablet;
- boolean transposeLayoutWithOrientation;
-
- int desiredWorkspaceLeftRightMarginPx;
- int edgeMarginPx;
- Rect defaultWidgetPadding;
-
- int widthPx;
- int heightPx;
- int availableWidthPx;
- int availableHeightPx;
- int iconSizePx;
- int iconTextSizePx;
- int cellWidthPx;
- int cellHeightPx;
- int folderBackgroundOffset;
- int folderIconSizePx;
- int folderCellWidthPx;
- int folderCellHeightPx;
- int hotseatCellWidthPx;
- int hotseatCellHeightPx;
- int hotseatIconSizePx;
- int hotseatBarHeightPx;
- int hotseatAllAppsRank;
- int allAppsNumRows;
- int allAppsNumCols;
- boolean searchBarVisible;
- int searchBarSpaceWidthPx;
- int searchBarSpaceMaxWidthPx;
- int searchBarSpaceHeightPx;
- int searchBarHeightPx;
- int pageIndicatorHeightPx;
-
- DeviceProfile(String n, float w, float h, float r, float c,
- float is, float its, float hs, float his) {
- // Ensure that we have an odd number of hotseat items (since we need to place all apps)
- if (!AppsCustomizePagedView.DISABLE_ALL_APPS && hs % 2 == 0) {
- throw new RuntimeException("All Device Profiles must have an odd number of hotseat spaces");
- }
-
- name = n;
- minWidthDps = w;
- minHeightDps = h;
- numRows = r;
- numColumns = c;
- iconSize = is;
- iconTextSize = its;
- numHotseatIcons = hs;
- hotseatIconSize = his;
- }
-
- DeviceProfile(Context context,
- ArrayList<DeviceProfile> profiles,
- float minWidth, float minHeight,
- int wPx, int hPx,
- int awPx, int ahPx,
- Resources resources) {
- DisplayMetrics dm = resources.getDisplayMetrics();
- ArrayList<DeviceProfileQuery> points =
- new ArrayList<DeviceProfileQuery>();
- transposeLayoutWithOrientation =
- resources.getBoolean(R.bool.hotseat_transpose_layout_with_orientation);
- minWidthDps = minWidth;
- minHeightDps = minHeight;
-
- ComponentName cn = new ComponentName(context.getPackageName(),
- this.getClass().getName());
- defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
- edgeMarginPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
- desiredWorkspaceLeftRightMarginPx = 2 * edgeMarginPx;
- pageIndicatorHeightPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_page_indicator_height);
-
- // Interpolate the rows
- for (DeviceProfile p : profiles) {
- points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numRows));
- }
- numRows = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
- // Interpolate the columns
- points.clear();
- for (DeviceProfile p : profiles) {
- points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numColumns));
- }
- numColumns = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
- // Interpolate the icon size
- points.clear();
- for (DeviceProfile p : profiles) {
- points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.iconSize));
- }
- iconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
- iconSizePx = DynamicGrid.pxFromDp(iconSize, dm);
-
- // Interpolate the icon text size
- points.clear();
- for (DeviceProfile p : profiles) {
- points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.iconTextSize));
- }
- iconTextSize = invDistWeightedInterpolate(minWidth, minHeight, points);
- iconTextSizePx = DynamicGrid.pxFromSp(iconTextSize, dm);
-
- // Interpolate the hotseat size
- points.clear();
- for (DeviceProfile p : profiles) {
- points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numHotseatIcons));
- }
- numHotseatIcons = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
- // Interpolate the hotseat icon size
- points.clear();
- for (DeviceProfile p : profiles) {
- points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.hotseatIconSize));
- }
- // Hotseat
- hotseatIconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
- hotseatIconSizePx = DynamicGrid.pxFromDp(hotseatIconSize, dm);
- hotseatAllAppsRank = (int) Math.ceil(numColumns / 2);
-
- // Calculate other vars based on Configuration
- updateFromConfiguration(resources, wPx, hPx, awPx, ahPx);
-
- // Search Bar
- searchBarVisible = SettingsProvider.getBoolean(context, SettingsProvider.SETTINGS_UI_HOMESCREEN_SEARCH,
- R.bool.preferences_interface_homescreen_search_default);
- searchBarSpaceMaxWidthPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_max_width);
- searchBarHeightPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_height);
-
- searchBarSpaceHeightPx = searchBarHeightPx + (searchBarVisible ? 2 * edgeMarginPx : 0);
-
- // Calculate the actual text height
- Paint textPaint = new Paint();
- textPaint.setTextSize(iconTextSizePx);
- FontMetrics fm = textPaint.getFontMetrics();
- cellWidthPx = iconSizePx;
- cellHeightPx = iconSizePx + (int) Math.ceil(fm.bottom - fm.top);
-
- // At this point, if the cells do not fit into the available height, then we need
- // to shrink the icon size
- /*
- Rect padding = getWorkspacePadding(isLandscape ?
- CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
- int h = (int) (numRows * cellHeightPx) + padding.top + padding.bottom;
- if (h > availableHeightPx) {
- float delta = h - availableHeightPx;
- int deltaPx = (int) Math.ceil(delta / numRows);
- iconSizePx -= deltaPx;
- iconSize = DynamicGrid.dpiFromPx(iconSizePx, dm);
- cellWidthPx = iconSizePx;
- cellHeightPx = iconSizePx + (int) Math.ceil(fm.bottom - fm.top);
- }
- */
-
- // Hotseat
- hotseatBarHeightPx = iconSizePx + 4 * edgeMarginPx;
- hotseatCellWidthPx = iconSizePx;
- hotseatCellHeightPx = iconSizePx;
-
- // Folder
- folderCellWidthPx = cellWidthPx + 3 * edgeMarginPx;
- folderCellHeightPx = cellHeightPx + (int) ((3f/2f) * edgeMarginPx);
- folderBackgroundOffset = -edgeMarginPx;
- folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset;
- }
-
- void updateFromConfiguration(Resources resources, int wPx, int hPx,
- int awPx, int ahPx) {
- isLandscape = (resources.getConfiguration().orientation ==
- Configuration.ORIENTATION_LANDSCAPE);
- isTablet = resources.getBoolean(R.bool.is_tablet);
- isLargeTablet = resources.getBoolean(R.bool.is_large_tablet);
- widthPx = wPx;
- heightPx = hPx;
- availableWidthPx = awPx;
- availableHeightPx = ahPx;
-
- Rect padding = getWorkspacePadding(isLandscape ?
- CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
- int pageIndicatorOffset =
- resources.getDimensionPixelSize(R.dimen.apps_customize_page_indicator_offset);
- if (isLandscape) {
- allAppsNumRows = (availableHeightPx - pageIndicatorOffset - 4 * edgeMarginPx) /
- (iconSizePx + iconTextSizePx + 2 * edgeMarginPx);
- } else {
- allAppsNumRows = (int) numRows + 1;
- }
- allAppsNumCols = (availableWidthPx - padding.left - padding.right - 2 * edgeMarginPx) /
- (iconSizePx + 2 * edgeMarginPx);
- allAppsNumCols = (int) Math.min(numColumns, allAppsNumCols);
-
- if (isPhone()) {
- searchBarSpaceWidthPx = Math.min(searchBarSpaceMaxWidthPx, widthPx);
- } else {
- searchBarSpaceWidthPx = wPx - (isLandscape ? 3 : 1) * iconSizePx;
- }
- }
-
- private float dist(PointF p0, PointF p1) {
- return (float) Math.sqrt((p1.x - p0.x)*(p1.x-p0.x) +
- (p1.y-p0.y)*(p1.y-p0.y));
- }
-
- private float weight(PointF a, PointF b,
- float pow) {
- float d = dist(a, b);
- if (d == 0f) {
- return Float.POSITIVE_INFINITY;
- }
- return (float) (1f / Math.pow(d, pow));
- }
-
- private float invDistWeightedInterpolate(float width, float height,
- ArrayList<DeviceProfileQuery> points) {
- float sum = 0;
- float weights = 0;
- float pow = 5;
- float kNearestNeighbors = 3;
- final PointF xy = new PointF(width, height);
-
- ArrayList<DeviceProfileQuery> pointsByNearness = points;
- Collections.sort(pointsByNearness, new Comparator<DeviceProfileQuery>() {
- public int compare(DeviceProfileQuery a, DeviceProfileQuery b) {
- return (int) (dist(xy, a.dimens) - dist(xy, b.dimens));
- }
- });
-
- for (int i = 0; i < pointsByNearness.size(); ++i) {
- DeviceProfileQuery p = pointsByNearness.get(i);
- if (i < kNearestNeighbors) {
- float w = weight(xy, p.dimens, pow);
- if (w == Float.POSITIVE_INFINITY) {
- return p.value;
- }
- weights += w;
- }
- }
-
- for (int i = 0; i < pointsByNearness.size(); ++i) {
- DeviceProfileQuery p = pointsByNearness.get(i);
- if (i < kNearestNeighbors) {
- float w = weight(xy, p.dimens, pow);
- sum += w * p.value / weights;
- }
- }
-
- return sum;
- }
-
- Rect getWorkspacePadding(int orientation) {
- Rect padding = new Rect();
- if (orientation == CellLayout.LANDSCAPE &&
- transposeLayoutWithOrientation) {
- // Pad the left and right of the workspace with search/hotseat bar sizes
- padding.set(searchBarVisible ? searchBarSpaceHeightPx : edgeMarginPx, edgeMarginPx,
- hotseatBarHeightPx, edgeMarginPx);
- } else {
- if (isTablet()) {
- // Pad the left and right of the workspace to ensure consistent spacing
- // between all icons
- int width = (orientation == CellLayout.LANDSCAPE)
- ? Math.max(widthPx, heightPx)
- : Math.min(widthPx, heightPx);
- // XXX: If the icon size changes across orientations, we will have to take
- // that into account here too.
- int gap = (int) ((width - 2 * edgeMarginPx -
- (numColumns * cellWidthPx)) / (2 * (numColumns + 1)));
- padding.set(edgeMarginPx + gap,
- searchBarVisible ? searchBarSpaceHeightPx : edgeMarginPx,
- edgeMarginPx + gap,
- hotseatBarHeightPx + pageIndicatorHeightPx);
- } else {
- // Pad the top and bottom of the workspace with search/hotseat bar sizes
- padding.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
- searchBarVisible ? searchBarSpaceHeightPx : edgeMarginPx,
- desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.right,
- hotseatBarHeightPx + pageIndicatorHeightPx);
- }
- }
- return padding;
- }
-
- // The rect returned will be extended to below the system ui that covers the workspace
- Rect getHotseatRect() {
- if (isVerticalBarLayout()) {
- return new Rect(availableWidthPx - hotseatBarHeightPx, 0,
- Integer.MAX_VALUE, availableHeightPx);
- } else {
- return new Rect(0, availableHeightPx - hotseatBarHeightPx,
- availableWidthPx, Integer.MAX_VALUE);
- }
- }
-
- int calculateCellWidth(int width, int countX) {
- return width / countX;
- }
- int calculateCellHeight(int height, int countY) {
- return height / countY;
- }
-
- boolean isPhone() {
- return !isTablet && !isLargeTablet;
- }
- boolean isTablet() {
- return isTablet;
- }
- boolean isLargeTablet() {
- return isLargeTablet;
- }
-
- boolean isVerticalBarLayout() {
- return isLandscape && transposeLayoutWithOrientation;
- }
-
- public void layout(Launcher launcher) {
- FrameLayout.LayoutParams lp;
- Resources res = launcher.getResources();
- boolean hasVerticalBarLayout = isVerticalBarLayout();
-
- // Layout the search bar space
- View searchBar = launcher.getSearchBar();
- lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams();
- if (hasVerticalBarLayout) {
- // Vertical search bar
- lp.gravity = Gravity.TOP | Gravity.LEFT;
- lp.width = searchBarSpaceHeightPx;
- lp.height = LayoutParams.MATCH_PARENT;
- searchBar.setPadding(
- 0, 2 * edgeMarginPx, 0,
- 2 * edgeMarginPx);
- } else {
- // Horizontal search bar
- lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
- lp.width = searchBarSpaceWidthPx;
- lp.height = searchBarSpaceHeightPx;
- searchBar.setPadding(
- 2 * edgeMarginPx,
- 2 * edgeMarginPx,
- 2 * edgeMarginPx, 0);
- }
- searchBar.setLayoutParams(lp);
-
- // Layout the drop target icons
- LinearLayout dropTargetBar = (LinearLayout) launcher.getSearchBar().getDropTargetBar();
- if (hasVerticalBarLayout) {
- dropTargetBar.setOrientation(LinearLayout.VERTICAL);
- } else {
- dropTargetBar.setOrientation(LinearLayout.HORIZONTAL);
- }
-
- // Layout the search bar
- View qsbBar = launcher.getQsbBar();
- qsbBar.setVisibility(searchBarVisible ? View.VISIBLE : View.GONE);
- LayoutParams vglp = qsbBar.getLayoutParams();
- vglp.width = LayoutParams.MATCH_PARENT;
- vglp.height = LayoutParams.MATCH_PARENT;
- qsbBar.setLayoutParams(vglp);
-
- // Layout the voice proxy
- View voiceButtonProxy = launcher.findViewById(R.id.voice_button_proxy);
- if (voiceButtonProxy != null) {
- if (hasVerticalBarLayout) {
- // TODO: MOVE THIS INTO SEARCH BAR MEASURE
- } else {
- lp = (FrameLayout.LayoutParams) voiceButtonProxy.getLayoutParams();
- lp.gravity = Gravity.TOP | Gravity.END;
- lp.width = (widthPx - searchBarSpaceWidthPx) / 2 +
- 2 * iconSizePx;
- lp.height = searchBarSpaceHeightPx;
- }
- }
-
- // Layout the workspace
- View workspace = launcher.findViewById(R.id.workspace);
- lp = (FrameLayout.LayoutParams) workspace.getLayoutParams();
- lp.gravity = Gravity.CENTER;
- Rect padding = getWorkspacePadding(isLandscape
- ? CellLayout.LANDSCAPE
- : CellLayout.PORTRAIT);
- workspace.setPadding(padding.left, padding.top,
- padding.right, padding.bottom);
- workspace.setLayoutParams(lp);
-
- // Layout the hotseat
- View hotseat = launcher.findViewById(R.id.hotseat);
- lp = (FrameLayout.LayoutParams) hotseat.getLayoutParams();
- if (hasVerticalBarLayout) {
- // Vertical hotseat
- lp.gravity = Gravity.RIGHT;
- lp.width = hotseatBarHeightPx;
- lp.height = LayoutParams.MATCH_PARENT;
- hotseat.setPadding(0, 2 * edgeMarginPx,
- 2 * edgeMarginPx, 2 * edgeMarginPx);
- } else if (isTablet()) {
- // Pad the hotseat with the grid gap calculated above
- int gridGap = (int) ((widthPx - 2 * edgeMarginPx -
- (numColumns * cellWidthPx)) / (2 * (numColumns + 1)));
- int gridWidth = (int) ((numColumns * cellWidthPx) +
- ((numColumns - 1) * gridGap));
- int hotseatGap = (int) Math.max(0,
- (gridWidth - (numHotseatIcons * hotseatCellWidthPx))
- / (numHotseatIcons - 1));
- lp.gravity = Gravity.BOTTOM;
- lp.width = LayoutParams.MATCH_PARENT;
- lp.height = hotseatBarHeightPx;
- hotseat.setPadding(2 * edgeMarginPx + gridGap + hotseatGap, 0,
- 2 * edgeMarginPx + gridGap + hotseatGap,
- 2 * edgeMarginPx);
- } else {
- // For phones, layout the hotseat without any bottom margin
- // to ensure that we have space for the folders
- lp.gravity = Gravity.BOTTOM;
- lp.width = LayoutParams.MATCH_PARENT;
- lp.height = hotseatBarHeightPx;
- hotseat.findViewById(R.id.layout).setPadding(2 * edgeMarginPx, 0,
- 2 * edgeMarginPx, 0);
- }
- hotseat.setLayoutParams(lp);
-
- // Layout the page indicators
- View pageIndicator = launcher.findViewById(R.id.page_indicator);
- if (pageIndicator != null) {
- if (hasVerticalBarLayout) {
- // Hide the page indicators when we have vertical search/hotseat
- pageIndicator.setVisibility(View.GONE);
- } else {
- // Put the page indicators above the hotseat
- lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams();
- lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
- lp.width = LayoutParams.WRAP_CONTENT;
- lp.height = LayoutParams.WRAP_CONTENT;
- lp.bottomMargin = hotseatBarHeightPx;
- pageIndicator.setLayoutParams(lp);
- }
- }
-
- // Layout the apps customize
- View appsCustomize = launcher.findViewById(R.id.apps_customize_pane_content);
- lp = (FrameLayout.LayoutParams) appsCustomize.getLayoutParams();
- lp.gravity = Gravity.CENTER;
- appsCustomize.setLayoutParams(lp);
- }
-}
public class DynamicGrid {
@SuppressWarnings("unused")
@@ -514,6 +33,10 @@ public class DynamicGrid {
private float mMinWidth;
private float mMinHeight;
+ // This is a static that we use for the default icon size on a 4/5-inch phone
+ static float DEFAULT_ICON_SIZE_DP = 60;
+ static float DEFAULT_ICON_SIZE_PX = 0;
+
public static float dpiFromPx(int size, DisplayMetrics metrics){
float densityRatio = (float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT;
return (size / densityRatio);
@@ -534,29 +57,34 @@ public class DynamicGrid {
DisplayMetrics dm = resources.getDisplayMetrics();
ArrayList<DeviceProfile> deviceProfiles =
new ArrayList<DeviceProfile>();
- boolean hasAA = !AppsCustomizePagedView.DISABLE_ALL_APPS;
+ boolean hasAA = !LauncherAppState.isDisableAllApps();
boolean useLargeIcons = SettingsProvider.getBoolean(context, SettingsProvider.SETTINGS_UI_GENERAL_ICONS_LARGE,
R.bool.preferences_interface_general_icons_large_default);
+ DEFAULT_ICON_SIZE_PX = pxFromDp(DEFAULT_ICON_SIZE_DP, dm);
// Our phone profiles include the bar sizes in each orientation
deviceProfiles.add(new DeviceProfile("Super Short Stubby",
- 255, 300, 2, 3, (useLargeIcons ? 54 : 48), 13, (hasAA ? 5 : 4), (useLargeIcons ? 54 : 48)));
+ 255, 300, 2, 3, (useLargeIcons ? 54 : 48), 13, (hasAA ? 5 : 5), (useLargeIcons ? 54 : 48)));
deviceProfiles.add(new DeviceProfile("Shorter Stubby",
- 255, 400, 3, 3, (useLargeIcons ? 54 : 48), 13, (hasAA ? 5 : 4), (useLargeIcons ? 54 : 48)));
+ 255, 400, 3, 3, (useLargeIcons ? 54 : 48), 13, (hasAA ? 5 : 5), (useLargeIcons ? 54 : 48)));
deviceProfiles.add(new DeviceProfile("Short Stubby",
- 275, 420, 3, 4, (useLargeIcons ? 54 : 48), 13, (hasAA ? 5 : 4), (useLargeIcons ? 54 : 48)));
+ 275, 420, 3, 4, (useLargeIcons ? 54 : 48), 13, (hasAA ? 5 : 5), (useLargeIcons ? 54 : 48)));
deviceProfiles.add(new DeviceProfile("Stubby",
- 255, 450, 3, 4, (useLargeIcons ? 54 : 48), 13, (hasAA ? 5 : 4), (useLargeIcons ? 54 : 48)));
- deviceProfiles.add(new DeviceProfile("Nexus S",
- 296, 491.33f, 4, 4, (useLargeIcons ? 56 : 48), 13, (hasAA ? 5 : 4), (useLargeIcons ? 56 : 48)));
+ 255, 450, 3, 4, (useLargeIcons ? 54 : 48), 13, (hasAA ? 5 : 5), (useLargeIcons ? 54 : 48)));
+ deviceProfiles.add(new DeviceProfile("Nexus 5",
+ 359, 567, 4, 4, (useLargeIcons ? DEFAULT_ICON_SIZE_DP : 56), 13, (hasAA ? 5 : 5), 56));
deviceProfiles.add(new DeviceProfile("Nexus 4",
- 359, 518, 4, 4, (useLargeIcons ? 60 : 52), 13, (hasAA ? 5 : 4), (useLargeIcons ? 56 : 48)));
+ 359, 518, 4, 4, (useLargeIcons ? DEFAULT_ICON_SIZE_DP : 52), 13, (hasAA ? 5 : 5), (useLargeIcons ? 56 : 48)));
+ deviceProfiles.add(new DeviceProfile("Large Phone",
+ 335, 567, 4, 4, (useLargeIcons ? DEFAULT_ICON_SIZE_DP : 56), 13, (hasAA ? 5 : 5), 56));
+ deviceProfiles.add(new DeviceProfile("Large Phone",
+ 406, 694, 5, 5, (useLargeIcons ? 64 : 56), 14.4f, 5, 56));
// The tablet profile is odd in that the landscape orientation
// also includes the nav bar on the side
deviceProfiles.add(new DeviceProfile("Nexus 7",
575, 904, 5, 5, (useLargeIcons ? 72 : 60), 14.4f, 7, (useLargeIcons ? 60 : 52)));
// Larger tablet profiles always have system bars on the top & bottom
deviceProfiles.add(new DeviceProfile("Nexus 10",
- 727, 1207, 5, 8, (useLargeIcons ? 80 : 64), 14.4f, 9, (useLargeIcons ? 64 : 56)));
+ 727, 1207, 5, 8, (useLargeIcons ? 76 : 64), 14.4f, 9, (useLargeIcons ? 64 : 56)));
/*
deviceProfiles.add(new DeviceProfile("Nexus 7",
600, 960, 5, 5, 72, 14.4f, 5, 60));
@@ -574,7 +102,7 @@ public class DynamicGrid {
resources);
}
- DeviceProfile getDeviceProfile() {
+ public DeviceProfile getDeviceProfile() {
return mProfile;
}
@@ -583,7 +111,7 @@ public class DynamicGrid {
"Wd: " + mProfile.minWidthDps + ", Hd: " + mProfile.minHeightDps +
", W: " + mProfile.widthPx + ", H: " + mProfile.heightPx +
" [r: " + mProfile.numRows + ", c: " + mProfile.numColumns +
- ", is: " + mProfile.iconSizePx + ", its: " + mProfile.iconTextSize +
+ ", is: " + mProfile.iconSizePx + ", its: " + mProfile.iconTextSizePx +
", cw: " + mProfile.cellWidthPx + ", ch: " + mProfile.cellHeightPx +
", hc: " + mProfile.numHotseatIcons + ", his: " + mProfile.hotseatIconSizePx + "]";
}
diff --git a/src/com/android/launcher3/DynamicGridSizeFragment.java b/src/com/android/launcher3/DynamicGridSizeFragment.java
new file mode 100644
index 000000000..586c2bd49
--- /dev/null
+++ b/src/com/android/launcher3/DynamicGridSizeFragment.java
@@ -0,0 +1,331 @@
+package com.android.launcher3;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.app.Dialog;
+import android.app.Fragment;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.util.DisplayMetrics;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.NumberPicker;
+import android.widget.TextView;
+import com.android.launcher3.settings.SettingsProvider;
+
+public class DynamicGridSizeFragment extends Fragment implements NumberPicker.OnValueChangeListener, Dialog.OnDismissListener{
+ public static final String DYNAMIC_GRID_SIZE_FRAGMENT = "dynamicGridSizeFragment";
+ public static final int MIN_DYNAMIC_GRID_ROWS = 2;
+ public static final int MIN_DYNAMIC_GRID_COLUMNS = 3;
+ ImageView mDynamicGridImage;
+ ListView mListView;
+ View mCurrentSelection;
+ GridSizeArrayAdapter mAdapter;
+ DeviceProfile.GridSize mCurrentSize;
+
+ Dialog mDialog;
+
+ int mCustomGridRows = 0;
+ int mCustomGridColumns = 0;
+
+ View.OnClickListener mSettingsItemListener = new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ mCurrentSize = DeviceProfile.GridSize
+ .getModeForValue((Integer) v.getTag());
+
+ setCleared(mCurrentSelection);
+ setSelected(v);
+ mCurrentSelection = v;
+
+ if (mCurrentSize == DeviceProfile.GridSize.Custom) {
+ showNumberPicker();
+ }
+
+ ((GridSizeArrayAdapter) mListView.getAdapter()).notifyDataSetChanged();
+
+ mAdapter.notifyDataSetInvalidated();
+ setCurrentImage();
+ }
+ };
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View v = inflater.inflate(R.layout.dynamic_grid_size_screen, container, false);
+ mDynamicGridImage = (ImageView) v.findViewById(R.id.dynamic_grid_size_image);
+ mDynamicGridImage.setBackground(getResources().getDrawable(R.drawable.grid));
+
+ LinearLayout titleLayout = (LinearLayout) v.findViewById(R.id.dynamic_grid_title);
+ titleLayout.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ setSize();
+ }
+ });
+
+ mCurrentSize = DeviceProfile.GridSize.getModeForValue(
+ SettingsProvider.getIntCustomDefault(getActivity(),
+ SettingsProvider.SETTINGS_UI_DYNAMIC_GRID_SIZE, 0));
+
+ setCurrentImage();
+
+ mListView = (ListView) v.findViewById(R.id.dynamic_grid_list);
+ Resources res = getResources();
+ String [] values = {
+ res.getString(R.string.grid_size_comfortable),
+ res.getString(R.string.grid_size_cozy),
+ res.getString(R.string.grid_size_condensed),
+ res.getString(R.string.grid_size_custom)};
+ mAdapter = new GridSizeArrayAdapter(getActivity(),
+ R.layout.settings_pane_list_item, values);
+ mListView.setAdapter(mAdapter);
+
+ return v;
+ }
+
+ private void setCurrentImage() {
+ Drawable d = null;
+ boolean custom = false;
+
+ switch (mCurrentSize) {
+ case Comfortable:
+ d = getResources().getDrawable(R.drawable.grid_comfortable);
+ break;
+ case Cozy:
+ d = getResources().getDrawable(R.drawable.grid_cozy);
+ break;
+ case Condensed:
+ d = getResources().getDrawable(R.drawable.grid_condensed);
+ break;
+ default:
+
+ custom = true;
+ break;
+ }
+
+ if (d != null && !custom) {
+ mDynamicGridImage.setImageBitmap(null);
+ mDynamicGridImage.setBackground(d);
+ } else if (custom) {
+ mDynamicGridImage.setBackground(null);
+ mDynamicGridImage.setImageBitmap(writeOnDrawable(R.drawable.grid));
+ }
+ }
+
+ public Bitmap writeOnDrawable(int drawableId){
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+
+ int rows = mCustomGridRows == 0 ? (int) grid.numRows : mCustomGridRows;
+ int columns = mCustomGridColumns == 0 ? (int) grid.numColumns : mCustomGridColumns;
+
+ String text = rows + " " + "\u00d7" + " " + columns;
+
+ Bitmap bm = BitmapFactory.decodeResource(getResources(),
+ drawableId).copy(Bitmap.Config.ARGB_8888, true);
+
+ Paint paint = new Paint();
+ paint.setStyle(Paint.Style.FILL);
+ paint.setColor(Color.BLACK);
+ int px = getResources().getDimensionPixelOffset(R.dimen.grid_custom_text);
+ paint.setTextSize(px);
+
+ Canvas canvas = new Canvas(bm);
+
+ float canvasWidth = canvas.getWidth();
+ float sentenceWidth = paint.measureText(text);
+ float startPositionX = (canvasWidth - sentenceWidth) / 2;
+
+ canvas.drawText(text, startPositionX, bm.getHeight()/2, paint);
+
+ return bm;
+ }
+
+ @Override
+ public Animator onCreateAnimator(int transit, boolean enter, int nextAnim) {
+ if (enter) {
+ DisplayMetrics displaymetrics = new DisplayMetrics();
+ getActivity().getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
+ int width = displaymetrics.widthPixels;
+ final ObjectAnimator anim = ObjectAnimator.ofFloat(this, "translationX", width, 0);
+
+ final View darkPanel = ((Launcher) getActivity()).getDarkPanel();
+ darkPanel.setVisibility(View.VISIBLE);
+ ObjectAnimator anim2 = ObjectAnimator.ofFloat(
+ darkPanel , "alpha", 0.0f, 0.3f);
+ anim2.start();
+
+ anim.addListener(new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator arg0) {}
+ @Override
+ public void onAnimationRepeat(Animator arg0) {}
+ @Override
+ public void onAnimationEnd(Animator arg0) {
+ darkPanel.setVisibility(View.GONE);
+ }
+ @Override
+ public void onAnimationCancel(Animator arg0) {}
+ });
+
+ return anim;
+ } else {
+ return super.onCreateAnimator(transit, enter, nextAnim);
+ }
+ }
+
+ public void setSize() {
+ ((Launcher) getActivity()).setDynamicGridSize(mCurrentSize);
+ }
+
+ private void setSelected(View v) {
+ v.setBackgroundColor(Color.WHITE);
+ TextView t = (TextView) v.findViewById(R.id.item_name);
+ t.setTextColor(getResources().getColor(R.color.settings_bg_color));
+ }
+
+ private void setCleared(View v) {
+ v.setBackgroundColor(getResources().getColor(R.color.settings_bg_color));
+ TextView t = (TextView) v.findViewById(R.id.item_name);
+ t.setTextColor(Color.WHITE);
+ }
+
+ private void showNumberPicker() {
+ mDialog = new Dialog(getActivity());
+ mDialog.setTitle(getResources().getString(R.string.preferences_interface_homescreen_custom));
+ mDialog.setContentView(R.layout.custom_grid_size_dialog);
+
+ NumberPicker nPRows= (NumberPicker) mDialog.findViewById(R.id.custom_rows);
+ NumberPicker nPColumns = (NumberPicker) mDialog.findViewById(R.id.custom_columns);
+
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+
+ int rows = grid.numRowsBase;
+ int columns = grid.numColumnsBase;
+ if (mCustomGridColumns == 0) {
+ mCustomGridColumns = (int) grid.numColumns;
+ }
+ if (mCustomGridRows == 0) {
+ mCustomGridRows = (int) grid.numRows;
+ }
+
+ nPRows.setMinValue(Math.max(MIN_DYNAMIC_GRID_ROWS, rows - DeviceProfile.GRID_SIZE_MIN));
+ nPRows.setMaxValue(rows + DeviceProfile.GRID_SIZE_MAX);
+ nPRows.setValue(mCustomGridRows);
+ nPRows.setWrapSelectorWheel(false);
+ nPRows.setOnValueChangedListener(this);
+ nPRows.setDescendantFocusability(NumberPicker.FOCUS_BLOCK_DESCENDANTS);
+
+ nPColumns.setMinValue(Math.max(MIN_DYNAMIC_GRID_COLUMNS, columns - DeviceProfile.GRID_SIZE_MIN));
+ nPColumns.setMaxValue(columns + DeviceProfile.GRID_SIZE_MAX);
+ nPColumns.setValue(mCustomGridColumns);
+ nPColumns.setWrapSelectorWheel(false);
+ nPColumns.setOnValueChangedListener(this);
+ nPColumns.setDescendantFocusability(NumberPicker.FOCUS_BLOCK_DESCENDANTS);
+
+ Button b = (Button) mDialog.findViewById(R.id.dialog_confirm_button);
+ b.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mDialog != null) {
+ mDialog.dismiss();
+ }
+ }
+ });
+ mDialog.setOnDismissListener(this);
+ mDialog.show();
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ if (mDialog != null) {
+ mDialog.dismiss();
+ }
+ }
+
+ @Override
+ public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
+ if (picker.getId() == R.id.custom_rows) {
+ mCustomGridRows = newVal;
+ } else if (picker.getId() == R.id.custom_columns) {
+ mCustomGridColumns = newVal;
+ }
+ }
+
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ SettingsProvider.putInt(getActivity(),
+ SettingsProvider.SETTINGS_UI_HOMESCREEN_ROWS, mCustomGridRows);
+ SettingsProvider.putInt(getActivity(),
+ SettingsProvider.SETTINGS_UI_HOMESCREEN_COLUMNS, mCustomGridColumns);
+
+ mAdapter.notifyDataSetInvalidated();
+
+ setCurrentImage();
+ }
+
+ private class GridSizeArrayAdapter extends ArrayAdapter<String> {
+ Context mContext;
+ String[] mTitles;
+
+ public GridSizeArrayAdapter(Context context, int textViewResourceId,
+ String[] objects) {
+ super(context, textViewResourceId, objects);
+
+ mContext = context;
+ mTitles = objects;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ LayoutInflater inflater = (LayoutInflater) mContext
+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ convertView = inflater.inflate(R.layout.settings_pane_list_item,
+ parent, false);
+ TextView textView = (TextView) convertView
+ .findViewById(R.id.item_name);
+ textView.setText(mTitles[position]);
+ // Set Selected State
+ if (position == mCurrentSize.getValue()) {
+ mCurrentSelection = convertView;
+ setSelected(mCurrentSelection);
+ }
+
+ if (position == DeviceProfile.GridSize.Custom.getValue()) {
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+
+ String state = mTitles[position];
+ int rows = SettingsProvider.getIntCustomDefault(getActivity(),
+ SettingsProvider.SETTINGS_UI_HOMESCREEN_ROWS, grid.numRowsBase);
+ int columns = SettingsProvider.getIntCustomDefault(getActivity(),
+ SettingsProvider.SETTINGS_UI_HOMESCREEN_COLUMNS, grid.numColumnsBase);
+ state += " " + "(" + rows + " " + "\u00d7" + " " + columns + ")";
+
+ textView.setText(state);
+ }
+
+ convertView.setOnClickListener(mSettingsItemListener);
+ convertView.setTag(position);
+ return convertView;
+ }
+ }
+}
diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java
index 8061c619d..847a8e1bc 100644
--- a/src/com/android/launcher3/FastBitmapDrawable.java
+++ b/src/com/android/launcher3/FastBitmapDrawable.java
@@ -35,8 +35,7 @@ class FastBitmapDrawable extends Drawable {
mAlpha = 255;
mBitmap = b;
if (b != null) {
- mWidth = mBitmap.getWidth();
- mHeight = mBitmap.getHeight();
+ setBounds(0, 0, b.getWidth(), b.getHeight());
} else {
mWidth = mHeight = 0;
}
@@ -67,6 +66,7 @@ class FastBitmapDrawable extends Drawable {
public void setFilterBitmap(boolean filterBitmap) {
mPaint.setFilterBitmap(filterBitmap);
+ mPaint.setAntiAlias(filterBitmap);
}
public int getAlpha() {
@@ -75,32 +75,30 @@ class FastBitmapDrawable extends Drawable {
@Override
public int getIntrinsicWidth() {
- return mWidth;
+ int width = getBounds().width();
+ if (width == 0) {
+ width = mBitmap.getWidth();
+ }
+ return width;
}
@Override
public int getIntrinsicHeight() {
- return mHeight;
+ int height = getBounds().height();
+ if (height == 0) {
+ height = mBitmap.getHeight();
+ }
+ return height;
}
@Override
public int getMinimumWidth() {
- return mWidth;
+ return getBounds().width();
}
@Override
public int getMinimumHeight() {
- return mHeight;
- }
-
- public void setBitmap(Bitmap b) {
- mBitmap = b;
- if (b != null) {
- mWidth = mBitmap.getWidth();
- mHeight = mBitmap.getHeight();
- } else {
- mWidth = mHeight = 0;
- }
+ return getBounds().height();
}
public Bitmap getBitmap() {
diff --git a/src/com/android/launcher3/FirstFrameAnimatorHelper.java b/src/com/android/launcher3/FirstFrameAnimatorHelper.java
index 78fdadd4f..f4c49d77a 100644
--- a/src/com/android/launcher3/FirstFrameAnimatorHelper.java
+++ b/src/com/android/launcher3/FirstFrameAnimatorHelper.java
@@ -91,29 +91,30 @@ public class FirstFrameAnimatorHelper extends AnimatorListenerAdapter
mStartTime = currentTime;
}
+ final long currentPlayTime = animation.getCurrentPlayTime();
if (!mHandlingOnAnimationUpdate &&
sVisible &&
// If the current play time exceeds the duration, the animation
// will get finished, even if we call setCurrentPlayTime -- therefore
// don't adjust the animation in that case
- animation.getCurrentPlayTime() < animation.getDuration()) {
+ currentPlayTime < animation.getDuration()) {
mHandlingOnAnimationUpdate = true;
long frameNum = sGlobalFrameCounter - mStartFrame;
// If we haven't drawn our first frame, reset the time to t = 0
// (give up after MAX_DELAY ms of waiting though - might happen, for example, if we
// are no longer in the foreground and no frames are being rendered ever)
- if (frameNum == 0 && currentTime < mStartTime + MAX_DELAY) {
+ if (frameNum == 0 && currentTime < mStartTime + MAX_DELAY && currentPlayTime > 0) {
// The first frame on animations doesn't always trigger an invalidate...
// force an invalidate here to make sure the animation continues to advance
mTarget.getRootView().invalidate();
animation.setCurrentPlayTime(0);
-
// For the second frame, if the first frame took more than 16ms,
// adjust the start time and pretend it took only 16ms anyway. This
// prevents a large jump in the animation due to an expensive first frame
} else if (frameNum == 1 && currentTime < mStartTime + MAX_DELAY &&
!mAdjustedSecondFrameTime &&
- currentTime > mStartTime + IDEAL_FRAME_DURATION) {
+ currentTime > mStartTime + IDEAL_FRAME_DURATION &&
+ currentPlayTime > IDEAL_FRAME_DURATION) {
animation.setCurrentPlayTime(IDEAL_FRAME_DURATION);
mAdjustedSecondFrameTime = true;
} else {
diff --git a/src/com/android/launcher3/FocusHelper.java b/src/com/android/launcher3/FocusHelper.java
index 357af49f6..b92b17f22 100644
--- a/src/com/android/launcher3/FocusHelper.java
+++ b/src/com/android/launcher3/FocusHelper.java
@@ -548,7 +548,7 @@ public class FocusHelper {
final CellLayout layout = (CellLayout) parent.getParent();
final Workspace workspace = (Workspace) layout.getParent();
final ViewGroup launcher = (ViewGroup) workspace.getParent();
- final ViewGroup tabs = (ViewGroup) launcher.findViewById(R.id.qsb_bar);
+ final ViewGroup tabs = (ViewGroup) launcher.findViewById(R.id.search_drop_target_bar);
final ViewGroup hotseat = (ViewGroup) launcher.findViewById(R.id.hotseat);
int pageIndex = workspace.indexOfChild(layout);
int pageCount = workspace.getChildCount();
diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java
index a00f3c1da..71183c342 100644
--- a/src/com/android/launcher3/Folder.java
+++ b/src/com/android/launcher3/Folder.java
@@ -20,11 +20,13 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.content.res.Resources;
import android.graphics.PointF;
import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
+import android.os.Bundle;
import android.os.SystemClock;
import android.support.v4.widget.AutoScrollHelper;
import android.text.InputType;
@@ -43,7 +45,9 @@ import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
+import android.widget.ImageView;
import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
import android.widget.ScrollView;
import android.widget.TextView;
@@ -62,6 +66,12 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
View.OnFocusChangeListener {
private static final String TAG = "Launcher.Folder";
+ private static final String PROTECTED_ACTION = "cyanogenmod.intent.action.PACKAGE_PROTECTED";
+ private static final String PROTECTED_STATE =
+ "cyanogenmod.intent.action.PACKAGE_PROTECTED_STATE";
+ private static final String PROTECTED_COMPONENT =
+ "cyanogenmod.intent.action.PACKAGE_PROTECTED_COMPONENT";
+
protected DragController mDragController;
protected Launcher mLauncher;
protected FolderInfo mInfo;
@@ -71,6 +81,8 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
static final int STATE_ANIMATING = 1;
static final int STATE_OPEN = 2;
+ private static final int CLOSE_FOLDER_DELAY_MS = 150;
+
private int mExpandDuration;
protected CellLayout mContent;
private ScrollView mScrollView;
@@ -79,17 +91,17 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
private int mState = STATE_NONE;
private static final int REORDER_ANIMATION_DURATION = 230;
private static final int REORDER_DELAY = 250;
- private static final int ON_EXIT_CLOSE_DELAY = 800;
+ private static final int ON_EXIT_CLOSE_DELAY = 400;
private boolean mRearrangeOnClose = false;
private FolderIcon mFolderIcon;
private int mMaxCountX;
private int mMaxCountY;
private int mMaxNumItems;
private ArrayList<View> mItemsInReadingOrder = new ArrayList<View>();
- private Drawable mIconDrawable;
boolean mItemsInvalidated = false;
private ShortcutInfo mCurrentDragInfo;
private View mCurrentDragView;
+ private boolean mIsExternalDrag;
boolean mSuppressOnAdd = false;
private int[] mTargetCell = new int[2];
private int[] mPreviousTargetCell = new int[2];
@@ -103,6 +115,8 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
private boolean mSuppressFolderDeletion = false;
private boolean mItemAddedBackToSelfViaIcon = false;
FolderEditText mFolderName;
+ ImageView mFolderLock;
+ RelativeLayout mFolderTitleSection;
private float mFolderIconPivotX;
private float mFolderIconPivotY;
@@ -116,6 +130,11 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
private int DRAG_MODE_REORDER = 1;
private int mDragMode = DRAG_MODE_NONE;
+ // We avoid measuring the scroll view with a 0 width or height, as this
+ // results in CellLayout being measured as UNSPECIFIED, which it does
+ // not support.
+ private static final int MIN_CONTENT_DIMEN = 5;
+
private boolean mDestroyed;
private AutoScrollHelper mAutoScrollHelper;
@@ -124,6 +143,8 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
private boolean mDeferDropAfterUninstall;
private boolean mUninstallSuccessful;
+ private boolean mHiddenFolder = false;
+
/**
* Used to inflate the Workspace from XML.
*
@@ -141,8 +162,13 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
Resources res = getResources();
mMaxCountX = (int) grid.numColumns;
- mMaxCountY = (int) grid.numRows;
- mMaxNumItems = mMaxCountX * mMaxCountY;
+ // Allow scrolling folders when DISABLE_ALL_APPS is true.
+ if (LauncherAppState.isDisableAllApps()) {
+ mMaxCountY = mMaxNumItems = Integer.MAX_VALUE;
+ } else {
+ mMaxCountY = (int) grid.numRows;
+ mMaxNumItems = mMaxCountX * mMaxCountY;
+ }
mInputMethodManager = (InputMethodManager)
getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
@@ -167,6 +193,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
super.onFinishInflate();
mScrollView = (ScrollView) findViewById(R.id.scroll_view);
mContent = (CellLayout) findViewById(R.id.folder_content);
+ int measureSpec = MeasureSpec.UNSPECIFIED;
LauncherAppState app = LauncherAppState.getInstance();
DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
@@ -181,7 +208,6 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
// We find out how tall the text view wants to be (it is set to wrap_content), so that
// we can allocate the appropriate amount of space for it.
- int measureSpec = MeasureSpec.UNSPECIFIED;
mFolderName.measure(measureSpec, measureSpec);
mFolderNameHeight = mFolderName.getMeasuredHeight();
@@ -199,6 +225,12 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
mFolderName.setVisibility(View.GONE);
mFolderNameHeight = getPaddingBottom();
}
+
+ mFolderLock = (ImageView) findViewById(R.id.folder_lock);
+ mFolderTitleSection = (RelativeLayout) findViewById(R.id.folder_title_section);
+ mFolderLock.measure(measureSpec, measureSpec);
+ mFolderLock.setOnClickListener(this);
+ mFolderTitleSection.measure(measureSpec, measureSpec);
}
private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {
@@ -223,6 +255,60 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
if (tag instanceof ShortcutInfo) {
mLauncher.onClick(v);
}
+
+ if (v.getId() == R.id.folder_lock) {
+ startHiddenFolderManager();
+ }
+ }
+
+ public void startHiddenFolderManager() {
+ Bundle bundle = new Bundle();
+ bundle.putBoolean(HiddenFolderFragment.HIDDEN_FOLDER_STATUS, mInfo.hidden);
+ mLauncher.validateLockForHiddenFolders(bundle, mFolderIcon);
+ }
+
+ public String[] getComponentTitles() {
+ int size = mItemsInReadingOrder.size();
+ String[] componentsTitles = new String[size];
+ for (int i = 0; i < size; i++) {
+ View v = mItemsInReadingOrder.get(i);
+ Object tag = v.getTag();
+ if (tag instanceof ShortcutInfo) {
+ componentsTitles[i] = ((ShortcutInfo) tag).title.toString();
+ }
+ }
+ return componentsTitles;
+ }
+
+ public String[] getComponents() {
+ String components = getComponentString();
+ return components.split("\\|");
+ }
+
+ public void modifyProtectedApps(boolean protect) {
+ String components = getComponentString();
+
+ Intent intent = new Intent();
+ intent.setAction(PROTECTED_ACTION);
+ intent.putExtra(PROTECTED_STATE, protect);
+ intent.putExtra(PROTECTED_COMPONENT, components);
+
+ mLauncher.sendBroadcast(intent);
+ }
+
+ private String getComponentString() {
+ int size = mItemsInReadingOrder.size();
+ String components = "";
+ for (int i = 0; i < size; i++) {
+ View v = mItemsInReadingOrder.get(i);
+ Object tag = v.getTag();
+ if (tag instanceof ShortcutInfo) {
+ ComponentName componentName = ((ShortcutInfo) tag).getIntent().getComponent();
+ components += componentName.flattenToString() + "|";
+ }
+ }
+
+ return components;
}
public boolean onLongClick(View v) {
@@ -236,11 +322,10 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
return false;
}
- mLauncher.dismissFolderCling(null);
+ mLauncher.getLauncherClings().dismissFolderCling(null);
mLauncher.getWorkspace().onDragStartedWithItem(v);
mLauncher.getWorkspace().beginDragShared(v, this);
- mIconDrawable = ((TextView) v).getCompoundDrawables()[1];
mCurrentDragInfo = item;
mEmptyCell[0] = item.cellX;
@@ -262,6 +347,8 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
public void startEditingFolderName() {
mFolderName.setHint("");
mIsEditingName = true;
+
+ mInputMethodManager.showSoftInput(mFolderName, 0);
}
public void dismissEditingName() {
@@ -305,10 +392,6 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
return mFolderName;
}
- public Drawable getDragDrawable() {
- return mIconDrawable;
- }
-
/**
* We need to handle touch events to prevent them from falling through to the workspace below.
*/
@@ -383,7 +466,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
int count = 0;
for (int i = 0; i < children.size(); i++) {
ShortcutInfo child = (ShortcutInfo) children.get(i);
- if (!createAndAddShortcut(child)) {
+ if (createAndAddShortcut(child) == null) {
overflow.add(child);
} else {
count++;
@@ -405,12 +488,26 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
updateTextViewFocus();
mInfo.addListener(this);
+ setFolderName();
+ updateItemLocationsInDatabase();
+ }
+
+ public void setFolderName() {
if (!sDefaultFolderName.contentEquals(mInfo.title)) {
mFolderName.setText(mInfo.title);
} else {
mFolderName.setText("");
}
updateItemLocationsInDatabase();
+
+ // In case any children didn't come across during loading, clean up the folder accordingly
+ mFolderIcon.post(new Runnable() {
+ public void run() {
+ if (getItemCount() <= 1) {
+ replaceFolderWithFinalItem();
+ }
+ }
+ });
}
/**
@@ -459,11 +556,15 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
public void onAnimationEnd(Animator animation) {
mState = STATE_OPEN;
setLayerType(LAYER_TYPE_NONE, null);
- Cling cling = mLauncher.showFirstRunFoldersCling();
- if (cling != null) {
- cling.bringScrimToFront();
- bringToFront();
- cling.bringToFront();
+
+ // Only show cling if we are not in the middle of a drag - this would be quite jarring.
+ if (!mDragController.isDragging()) {
+ Cling cling = mLauncher.getLauncherClings().showFoldersCling();
+ if (cling != null) {
+ cling.bringScrimToFront();
+ bringToFront();
+ cling.bringToFront();
+ }
}
setFocusOnFirstChild();
}
@@ -471,6 +572,23 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
oa.setDuration(mExpandDuration);
setLayerType(LAYER_TYPE_HARDWARE, null);
oa.start();
+
+ // Make sure the folder picks up the last drag move even if the finger doesn't move.
+ if (mDragController.isDragging()) {
+ mDragController.forceTouchMove();
+ }
+ }
+
+ public void beginExternalDrag(ShortcutInfo item) {
+ setupContentForNumItems(getItemCount() + 1);
+ findAndSetEmptyCells(item);
+
+ mCurrentDragInfo = item;
+ mEmptyCell[0] = item.cellX;
+ mEmptyCell[1] = item.cellY;
+ mIsExternalDrag = true;
+
+ mDragInProgress = true;
}
private void sendCustomAccessibilityEvent(int type, String text) {
@@ -537,7 +655,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
}
}
- protected boolean createAndAddShortcut(ShortcutInfo item) {
+ protected View createAndAddShortcut(ShortcutInfo item) {
final BubbleTextView textView =
(BubbleTextView) mInflater.inflate(R.layout.application, this, false);
textView.setCompoundDrawables(null,
@@ -546,7 +664,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
textView.setTag(item);
textView.setTextColor(getResources().getColor(R.color.folder_items_text_color));
textView.setShadowsEnabled(false);
- Utilities.applyTypeface(textView);
+ textView.setGlowColor(getResources().getColor(R.color.folder_items_glow_color));
textView.setOnClickListener(this);
textView.setOnLongClickListener(this);
@@ -558,7 +676,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
// This shouldn't happen, log it.
Log.e(TAG, "Folder order not properly persisted during bind");
if (!findAndSetEmptyCells(item)) {
- return false;
+ return null;
}
}
@@ -567,7 +685,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
boolean insert = false;
textView.setOnKeyListener(new FolderKeyEventListener());
mContent.addViewToCellLayout(textView, insert ? 0 : -1, (int)item.id, lp, true);
- return true;
+ return textView;
}
public void onDragEnter(DragObject d) {
@@ -716,6 +834,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
mCurrentDragView = null;
mSuppressOnAdd = false;
mRearrangeOnClose = true;
+ mIsExternalDrag = false;
}
public void onDragExit(DragObject d) {
@@ -749,7 +868,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
success && (!beingCalledAfterUninstall || mUninstallSuccessful);
if (successfulDrop) {
- if (mDeleteFolderOnDropCompleted && !mItemAddedBackToSelfViaIcon) {
+ if (mDeleteFolderOnDropCompleted && !mItemAddedBackToSelfViaIcon && target != this) {
replaceFolderWithFinalItem();
}
} else {
@@ -768,6 +887,12 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
}
}
+ // This is kind of hacky, but in general, dropping on the workspace handles removing
+ // the extra screen, but dropping elsewhere (back to self, or onto delete) doesn't.
+ if (target != mLauncher.getWorkspace()) {
+ mLauncher.getWorkspace().removeExtraEmptyScreen(true, null);
+ }
+
mDeleteFolderOnDropCompleted = false;
mDragInProgress = false;
mItemAddedBackToSelfViaIcon = false;
@@ -793,10 +918,25 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
}
@Override
+ public float getIntrinsicIconScaleFactor() {
+ return 1f;
+ }
+
+ @Override
public boolean supportsFlingToDelete() {
return true;
}
+ @Override
+ public boolean supportsAppInfoDropTarget() {
+ return false;
+ }
+
+ @Override
+ public boolean supportsDeleteDropTarget() {
+ return true;
+ }
+
public void onFlingToDelete(DragObject d, int x, int y, PointF vec) {
// Do nothing
}
@@ -812,7 +952,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
View v = list.get(i);
ItemInfo info = (ItemInfo) v.getTag();
LauncherModel.moveItemInDatabase(mLauncher, info, mInfo.id, 0,
- info.cellX, info.cellY);
+ info.cellX, info.cellY);
}
}
@@ -965,31 +1105,48 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
Rect workspacePadding = grid.getWorkspacePadding(grid.isLandscape ?
CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
int maxContentAreaHeight = grid.availableHeightPx -
- 4 * grid.edgeMarginPx -
workspacePadding.top - workspacePadding.bottom -
- getPaddingTop() - getPaddingBottom() -
mFolderNameHeight;
- return Math.min(maxContentAreaHeight,
+ int height = Math.min(maxContentAreaHeight,
mContent.getDesiredHeight());
+ return Math.max(height, MIN_CONTENT_DIMEN);
+ }
+
+ private int getContentAreaWidth() {
+ return Math.max(mContent.getDesiredWidth(), MIN_CONTENT_DIMEN);
}
private int getFolderHeight() {
- int height = getPaddingTop() + getPaddingBottom()
- + getContentAreaHeight() + mFolderNameHeight;
+ int height = getPaddingTop() + getPaddingBottom() + mFolderNameHeight
+ + getContentAreaHeight();
return height;
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth();
+ int width = getPaddingLeft()
+ + getPaddingRight()
+ + Math.max(mContent.getDesiredWidth(),
+ mFolderTitleSection.getMeasuredWidth());
int height = getFolderHeight();
- int contentAreaWidthSpec = MeasureSpec.makeMeasureSpec(mContent.getDesiredWidth(),
+ int contentAreaWidthSpec = MeasureSpec.makeMeasureSpec(getContentAreaWidth(),
MeasureSpec.EXACTLY);
int contentAreaHeightSpec = MeasureSpec.makeMeasureSpec(getContentAreaHeight(),
MeasureSpec.EXACTLY);
- mContent.setFixedSize(mContent.getDesiredWidth(), mContent.getDesiredHeight());
+
+ if (LauncherAppState.isDisableAllApps()) {
+ // Don't cap the height of the content to allow scrolling.
+ mContent.setFixedSize(getContentAreaWidth(), mContent.getDesiredHeight());
+ } else {
+ mContent.setFixedSize(getContentAreaWidth(), getContentAreaHeight());
+ }
+
mScrollView.measure(contentAreaWidthSpec, contentAreaHeightSpec);
- mFolderName.measure(contentAreaWidthSpec,
- MeasureSpec.makeMeasureSpec(mFolderNameHeight, MeasureSpec.EXACTLY));
+ mFolderName.measure(contentAreaWidthSpec, MeasureSpec.makeMeasureSpec(
+ mFolderNameHeight, MeasureSpec.EXACTLY));
+ mFolderLock.measure(contentAreaWidthSpec, MeasureSpec.makeMeasureSpec(
+ mFolderNameHeight, MeasureSpec.EXACTLY));
+ mFolderTitleSection.measure(contentAreaWidthSpec, MeasureSpec
+ .makeMeasureSpec(mFolderNameHeight, MeasureSpec.EXACTLY));
setMeasuredDimension(width, height);
}
@@ -1051,13 +1208,16 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
}
private void replaceFolderWithFinalItem() {
+ if (mInfo.hidden) {
+ return;
+ }
// Add the last remaining child to the workspace in place of the folder
Runnable onCompleteRunnable = new Runnable() {
@Override
public void run() {
CellLayout cellLayout = mLauncher.getCellLayout(mInfo.container, mInfo.screenId);
- View child = null;
+ View child = null;
// Move the item from the folder to the workspace, in the position of the folder
if (getItemCount() == 1) {
ShortcutInfo finalItem = mInfo.contents.get(0);
@@ -1069,7 +1229,10 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
if (getItemCount() <= 1) {
// Remove the folder
LauncherModel.deleteItemFromDatabase(mLauncher, mInfo);
- cellLayout.removeView(mFolderIcon);
+ if (cellLayout != null) {
+ // b/12446428 -- sometimes the cell layout has already gone away?
+ cellLayout.removeView(mFolderIcon);
+ }
if (mFolderIcon instanceof DropTarget) {
mDragController.removeDropTarget((DropTarget) mFolderIcon);
}
@@ -1087,6 +1250,8 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
View finalChild = getItemAt(0);
if (finalChild != null) {
mFolderIcon.performDestroyAnimation(finalChild, onCompleteRunnable);
+ } else {
+ onCompleteRunnable.run();
}
mDestroyed = true;
}
@@ -1109,34 +1274,75 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
}
public void onDrop(DragObject d) {
- ShortcutInfo item;
- if (d.dragInfo instanceof AppInfo) {
- // Came from all apps -- make a copy
- item = ((AppInfo) d.dragInfo).makeShortcut();
- item.spanX = 1;
- item.spanY = 1;
- } else {
- item = (ShortcutInfo) d.dragInfo;
+ Runnable cleanUpRunnable = null;
+
+ // If we are coming from All Apps space, we need to remove the extra empty screen (which is
+ // normally done in Workspace#onDropExternal, as well zoom back in and close the folder.
+ if (d.dragSource != mLauncher.getWorkspace() && !(d.dragSource instanceof Folder)) {
+ cleanUpRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mLauncher.getWorkspace().removeExtraEmptyScreen(false, new Runnable() {
+ @Override
+ public void run() {
+ mLauncher.closeFolder();
+ mLauncher.exitSpringLoadedDragModeDelayed(true,
+ Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT_FOLDER_CLOSE,
+ null);
+ }
+ }, CLOSE_FOLDER_DELAY_MS, false);
+ }
+ };
}
- // Dragged from self onto self, currently this is the only path possible, however
- // we keep this as a distinct code path.
- if (item == mCurrentDragInfo) {
- ShortcutInfo si = (ShortcutInfo) mCurrentDragView.getTag();
- CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mCurrentDragView.getLayoutParams();
+
+ View currentDragView;
+ ShortcutInfo si = mCurrentDragInfo;
+ if (mIsExternalDrag) {
+ si.cellX = mEmptyCell[0];
+ si.cellY = mEmptyCell[1];
+ currentDragView = createAndAddShortcut(si);
+ } else {
+ currentDragView = mCurrentDragView;
+ CellLayout.LayoutParams lp = (CellLayout.LayoutParams) currentDragView.getLayoutParams();
si.cellX = lp.cellX = mEmptyCell[0];
si.cellX = lp.cellY = mEmptyCell[1];
- mContent.addViewToCellLayout(mCurrentDragView, -1, (int)item.id, lp, true);
- if (d.dragView.hasDrawn()) {
- mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, mCurrentDragView);
- } else {
- d.deferDragViewCleanupPostAnimation = false;
- mCurrentDragView.setVisibility(VISIBLE);
+ mContent.addViewToCellLayout(currentDragView, -1, (int) si.id, lp, true);
+ }
+
+ if (d.dragView.hasDrawn()) {
+
+ // Temporarily reset the scale such that the animation target gets calculated correctly.
+ float scaleX = getScaleX();
+ float scaleY = getScaleY();
+ setScaleX(1.0f);
+ setScaleY(1.0f);
+ mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, currentDragView,
+ cleanUpRunnable, null);
+ setScaleX(scaleX);
+ setScaleY(scaleY);
+ } else {
+ d.deferDragViewCleanupPostAnimation = false;
+ currentDragView.setVisibility(VISIBLE);
+ }
+ mItemsInvalidated = true;
+ setupContentDimensions(getItemCount());
+
+ // Actually move the item in the database if it was an external drag.
+ if (mIsExternalDrag) {
+ LauncherModel.addOrMoveItemInDatabase(
+ mLauncher, si, mInfo.id, 0, si.cellX, si.cellY);
+
+ // We only need to update the locations if it doesn't get handled in #onDropCompleted.
+ if (d.dragSource != this) {
+ updateItemLocationsInDatabaseBatch();
}
- mItemsInvalidated = true;
- setupContentDimensions(getItemCount());
- mSuppressOnAdd = true;
+ mIsExternalDrag = false;
}
- mInfo.add(item);
+
+ // Temporarily suppress the listener, as we did all the work already here.
+ mSuppressOnAdd = true;
+ mInfo.add(si);
+ mSuppressOnAdd = false;
}
// This is used so the item doesn't immediately appear in the folder when added. In one case
@@ -1218,6 +1424,20 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
return mItemsInReadingOrder;
}
+ public ShortcutInfo getShortcutForComponent(ComponentName componentName) {
+ for (View v : mItemsInReadingOrder) {
+ Object tag = v.getTag();
+ if (tag instanceof ShortcutInfo) {
+ ComponentName cName = ((ShortcutInfo) tag).getIntent().getComponent();
+ if (cName.equals(componentName)) {
+ return (ShortcutInfo) tag;
+ }
+ }
+ }
+
+ return null;
+ }
+
public void getLocationInDragLayer(int[] loc) {
mLauncher.getDragLayer().getLocationInDragLayer(this, loc);
}
@@ -1232,4 +1452,8 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
public void getHitRectRelativeToDragLayer(Rect outRect) {
getHitRect(outRect);
}
+
+ public View getViewFromPosition(int position) {
+ return mItemsInReadingOrder.get(position);
+ }
}
diff --git a/src/com/android/launcher3/FolderIcon.java b/src/com/android/launcher3/FolderIcon.java
index 9b7919b42..464b35da9 100644
--- a/src/com/android/launcher3/FolderIcon.java
+++ b/src/com/android/launcher3/FolderIcon.java
@@ -36,11 +36,10 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
+import android.widget.FrameLayout;
import android.widget.ImageView;
-import android.widget.LinearLayout;
import android.widget.TextView;
-import com.android.launcher3.R;
import com.android.launcher3.DropTarget.DragObject;
import com.android.launcher3.FolderInfo.FolderListener;
@@ -49,7 +48,7 @@ import java.util.ArrayList;
/**
* An icon that can appear on in the workspace representing an {@link UserFolder}.
*/
-public class FolderIcon extends LinearLayout implements FolderListener {
+public class FolderIcon extends FrameLayout implements FolderListener {
private Launcher mLauncher;
private Folder mFolder;
private FolderInfo mInfo;
@@ -76,10 +75,16 @@ public class FolderIcon extends LinearLayout implements FolderListener {
// Flag as to whether or not to draw an outer ring. Currently none is designed.
public static final boolean HAS_OUTER_RING = true;
+ // Flag whether the folder should open itself when an item is dragged over is enabled.
+ public static final boolean SPRING_LOADING_ENABLED = true;
+
// The degree to which the item in the back of the stack is scaled [0...1]
// (0 means it's not scaled at all, 1 means it's scaled to nothing)
private static final float PERSPECTIVE_SCALE_FACTOR = 0.35f;
+ // Delay when drag enters until the folder opens, in miliseconds.
+ private static final int ON_OPEN_DELAY = 800;
+
public static Drawable sSharedFolderLeaveBehind = null;
private ImageView mPreviewBackground;
@@ -104,6 +109,9 @@ public class FolderIcon extends LinearLayout implements FolderListener {
private PreviewItemDrawingParams mAnimParams = new PreviewItemDrawingParams(0, 0, 0, 0);
private ArrayList<ShortcutInfo> mHiddenItems = new ArrayList<ShortcutInfo>();
+ private Alarm mOpenAlarm = new Alarm();
+ private ItemInfo mDragInfo;
+
public FolderIcon(Context context, AttributeSet attrs) {
super(context, attrs);
init();
@@ -134,18 +142,20 @@ public class FolderIcon extends LinearLayout implements FolderListener {
"INITIAL_ITEM_ANIMATION_DURATION, as sequencing of adding first two items " +
"is dependent on this");
}
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
FolderIcon icon = (FolderIcon) LayoutInflater.from(launcher).inflate(resId, group, false);
icon.setClipToPadding(false);
icon.mFolderName = (BubbleTextView) icon.findViewById(R.id.folder_icon_name);
icon.mFolderName.setText(folderInfo.title);
- Utilities.applyTypeface(icon.mFolderName);
- icon.mPreviewBackground = (ImageView) icon.findViewById(R.id.preview_background);
- LauncherAppState app = LauncherAppState.getInstance();
- DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+ icon.mFolderName.setCompoundDrawablePadding(0);
+ FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) icon.mFolderName.getLayoutParams();
+ lp.topMargin = grid.iconSizePx + grid.iconDrawablePaddingPx;
+
// Offset the preview background to center this view accordingly
- LinearLayout.LayoutParams lp =
- (LinearLayout.LayoutParams) icon.mPreviewBackground.getLayoutParams();
+ icon.mPreviewBackground = (ImageView) icon.findViewById(R.id.preview_background);
+ lp = (FrameLayout.LayoutParams) icon.mPreviewBackground.getLayoutParams();
lp.topMargin = grid.folderBackgroundOffset;
lp.width = grid.folderIconSizePx;
lp.height = grid.folderIconSizePx;
@@ -308,14 +318,23 @@ public class FolderIcon extends LinearLayout implements FolderListener {
private boolean willAcceptItem(ItemInfo item) {
final int itemType = item.itemType;
+
+ boolean hidden = false;
+ if (item instanceof FolderInfo){
+ hidden = ((FolderInfo) item).hidden;
+ }
return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT ||
itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) &&
- !mFolder.isFull() && item != mInfo && !mInfo.opened);
+ !mFolder.isFull() && item != mInfo && !mInfo.opened &&
+ !hidden);
}
public boolean acceptDrop(Object dragInfo) {
final ItemInfo item = (ItemInfo) dragInfo;
+ if (mInfo.hidden) {
+ return false;
+ }
return !mFolder.isDestroyed() && willAcceptItem(item);
}
@@ -331,11 +350,32 @@ public class FolderIcon extends LinearLayout implements FolderListener {
mFolderRingAnimator.setCellLayout(layout);
mFolderRingAnimator.animateToAcceptState();
layout.showFolderAccept(mFolderRingAnimator);
+ mOpenAlarm.setOnAlarmListener(mOnOpenListener);
+ if (SPRING_LOADING_ENABLED) {
+ mOpenAlarm.setAlarm(ON_OPEN_DELAY);
+ }
+ mDragInfo = (ItemInfo) dragInfo;
}
public void onDragOver(Object dragInfo) {
}
+ OnAlarmListener mOnOpenListener = new OnAlarmListener() {
+ public void onAlarm(Alarm alarm) {
+ ShortcutInfo item;
+ if (mDragInfo instanceof AppInfo) {
+ // Came from all apps -- make a copy.
+ item = ((AppInfo) mDragInfo).makeShortcut();
+ item.spanX = 1;
+ item.spanY = 1;
+ } else {
+ item = (ShortcutInfo) mDragInfo;
+ }
+ mFolder.beginExternalDrag(item);
+ mLauncher.openFolder(FolderIcon.this);
+ }
+ };
+
public void performCreateAnimation(final ShortcutInfo destInfo, final View destView,
final ShortcutInfo srcInfo, final DragView srcView, Rect dstRect,
float scaleRelativeToDragLayer, Runnable postAnimationRunnable) {
@@ -371,6 +411,7 @@ public class FolderIcon extends LinearLayout implements FolderListener {
public void onDragExit() {
mFolderRingAnimator.animateToNaturalState();
+ mOpenAlarm.cancelAlarm();
}
private void onDrop(final ShortcutInfo item, DragView animateView, Rect finalRect,
@@ -548,12 +589,10 @@ public class FolderIcon extends LinearLayout implements FolderListener {
if (d != null) {
mOldBounds.set(d.getBounds());
d.setBounds(0, 0, mIntrinsicIconSize, mIntrinsicIconSize);
- d.setFilterBitmap(true);
d.setColorFilter(Color.argb(params.overlayAlpha, 255, 255, 255),
PorterDuff.Mode.SRC_ATOP);
d.draw(canvas);
d.clearColorFilter();
- d.setFilterBitmap(false);
d.setBounds(mOldBounds);
}
canvas.restore();
@@ -580,6 +619,20 @@ public class FolderIcon extends LinearLayout implements FolderListener {
}
int nItemsInPreview = Math.min(items.size(), NUM_ITEMS_IN_PREVIEW);
+
+ // Hidden folder - don't display Preview
+ if (mInfo.hidden) {
+ mParams = computePreviewItemDrawingParams(NUM_ITEMS_IN_PREVIEW/2, mParams);
+ canvas.save();
+ canvas.translate(mParams.transX + mPreviewOffsetX, mParams.transY + mPreviewOffsetY);
+ canvas.scale(mParams.scale, mParams.scale);
+ Drawable lock = getResources().getDrawable(R.drawable.folder_lock);
+ lock.setBounds(0, 0, mIntrinsicIconSize, mIntrinsicIconSize);
+ lock.draw(canvas);
+ canvas.restore();
+ return;
+ }
+
if (!mAnimating) {
for (int i = nItemsInPreview - 1; i >= 0; i--) {
v = (TextView) items.get(i);
diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java
index bb5ae8200..133bfd089 100644
--- a/src/com/android/launcher3/FolderInfo.java
+++ b/src/com/android/launcher3/FolderInfo.java
@@ -16,10 +16,10 @@
package com.android.launcher3;
-import java.util.ArrayList;
-
import android.content.ContentValues;
+import java.util.ArrayList;
+
/**
* Represents a folder containing shortcuts or apps.
*/
@@ -31,9 +31,10 @@ class FolderInfo extends ItemInfo {
boolean opened;
/**
- * The apps and shortcuts
+ * The apps and shortcuts and hidden status
*/
ArrayList<ShortcutInfo> contents = new ArrayList<ShortcutInfo>();
+ Boolean hidden = false;
ArrayList<FolderListener> listeners = new ArrayList<FolderListener>();
@@ -78,6 +79,7 @@ class FolderInfo extends ItemInfo {
void onAddToDatabase(ContentValues values) {
super.onAddToDatabase(values);
values.put(LauncherSettings.Favorites.TITLE, title.toString());
+ values.put(LauncherSettings.Favorites.HIDDEN, hidden ? 1 : 0);
}
void addListener(FolderListener listener) {
diff --git a/src/com/android/launcher3/GelIntegrationHelper.java b/src/com/android/launcher3/GelIntegrationHelper.java
new file mode 100644
index 000000000..f43e7e6a0
--- /dev/null
+++ b/src/com/android/launcher3/GelIntegrationHelper.java
@@ -0,0 +1,102 @@
+package com.android.launcher3;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.service.gesture.EdgeGestureManager;
+import com.android.internal.util.gesture.EdgeGesturePosition;
+
+import java.util.List;
+
+/**
+ * A singleton wrapper class for GEL Integration.
+ * Requires EdgeGestureManager functionality that is only available
+ * in CyanogenMod.
+ */
+public class GelIntegrationHelper {
+ // The Intent for the search activity (resolves to Google Now when installed)
+ public final static String INTENT_ACTION_ASSIST = "android.intent.action.ASSIST";
+
+ private static final String GEL_ACTIVITY = "com.google.android.velvet.ui.VelvetActivity";
+ private static final String GEL_PACKAGE_NAME = "com.google.android.googlequicksearchbox";
+
+ private static final int EDGE_GESTURE_SERVICE_RIGHT_EDGE = 4;
+ private static final int EDGE_GESTURE_SERVICE_LEFT_EDGE = 1;
+ private static final int EDGE_GESTURE_SERVICE_NO_EDGE = -1;
+
+ private EdgeGestureManager.EdgeGestureActivationListener mEdgeGestureActivationListener = null;
+ private static GelIntegrationHelper sInstance;
+
+ private GelIntegrationHelper() {}
+
+ public static GelIntegrationHelper getInstance() {
+ if(sInstance == null) {
+ sInstance = new GelIntegrationHelper();
+ }
+ return sInstance;
+ }
+
+ /**
+ * 1. Registers an EdgeGestureActivationListener with the EdgeGestureManager so that
+ * the user can return to Trebuchet when they swipe from the right edge of the device.
+ * 2. Starts the Google Now Activity with an exit_out_right transition animation so that
+ * the new Activity appears to slide in as another screen (similar to GEL).
+ */
+ public void registerSwipeBackGestureListenerAndStartGel(final Activity launcherActivity, boolean isLayoutRtl) {
+ EdgeGestureManager edgeGestureManager = EdgeGestureManager.getInstance();
+ if(mEdgeGestureActivationListener == null) {
+ mEdgeGestureActivationListener = new EdgeGestureManager.EdgeGestureActivationListener() {
+ ActivityManager mAm = (ActivityManager)
+ launcherActivity.getSystemService(Activity.ACTIVITY_SERVICE);
+
+ @Override
+ public void onEdgeGestureActivation(int touchX, int touchY,
+ EdgeGesturePosition position, int flags) {
+ // Retrieve the top level activity information
+ List< ActivityManager.RunningTaskInfo > taskInfo = mAm.getRunningTasks(1);
+ ComponentName topActivityComponentInfo = taskInfo.get(0).topActivity;
+ String topActivityClassName = topActivityComponentInfo.getClassName();
+ String topActivityPackageName = topActivityComponentInfo.getPackageName();
+
+ // If the top level activity is Google Now, return to home.
+ // Otherwise, do nothing.
+ if(GEL_ACTIVITY.equals(topActivityClassName)
+ && GEL_PACKAGE_NAME.equals(topActivityPackageName)) {
+ Intent homeIntent = new Intent(Intent.ACTION_MAIN);
+ homeIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION);
+ homeIntent.addCategory(Intent.CATEGORY_HOME);
+ launcherActivity.startActivity(homeIntent);
+ launcherActivity.overridePendingTransition(0, 0);
+ dropEventsUntilLift();
+ }
+ }
+ };
+ edgeGestureManager.setEdgeGestureActivationListener(mEdgeGestureActivationListener);
+ }
+ mEdgeGestureActivationListener.restoreListenerState();
+ int edge = isLayoutRtl ? EDGE_GESTURE_SERVICE_LEFT_EDGE : EDGE_GESTURE_SERVICE_RIGHT_EDGE;
+ edgeGestureManager.updateEdgeGestureActivationListener(mEdgeGestureActivationListener,
+ edge);
+
+ // Start the Google Now Activity
+ Intent i = new Intent(INTENT_ACTION_ASSIST);
+ launcherActivity.startActivity(i);
+ launcherActivity.overridePendingTransition(0, R.anim.exit_out_right);
+ }
+
+ /**
+ * Handle necessary cleanup and reset tasks for GEL integration, to be called from onResume.
+ */
+ public void handleGelResume() {
+ // If there is an active EdgeGestureActivationListener for GEL integration,
+ // it should stop listening when we have resumed the launcher.
+ if(mEdgeGestureActivationListener != null) {
+ EdgeGestureManager edgeGestureManager = EdgeGestureManager.getInstance();
+ // Update the listener so it is not listening to any postions (-1)
+ edgeGestureManager.updateEdgeGestureActivationListener(mEdgeGestureActivationListener,
+ EDGE_GESTURE_SERVICE_NO_EDGE);
+ }
+ }
+
+}
diff --git a/src/com/android/launcher3/HiddenFolderFragment.java b/src/com/android/launcher3/HiddenFolderFragment.java
new file mode 100644
index 000000000..07b0e4a8a
--- /dev/null
+++ b/src/com/android/launcher3/HiddenFolderFragment.java
@@ -0,0 +1,343 @@
+package com.android.launcher3;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.media.Image;
+import android.text.InputType;
+import android.view.KeyEvent;
+import android.view.MenuItem;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.*;
+import com.android.launcher3.settings.SettingsProvider;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+
+public class HiddenFolderFragment extends Fragment {
+ public static final String HIDDEN_FOLDER_FRAGMENT = "hiddenFolderFragment";
+ public static final String HIDDEN_FOLDER_NAME = "hiddenFolderName";
+ public static final String HIDDEN_FOLDER_STATUS = "hiddenFolderStatus";
+ public static final String HIDDEN_FOLDER_INFO = "hiddenFolderInfo";
+ public static final String HIDDEN_FOLDER_INFO_TITLES = "hiddenFolderInfoTitles";
+ public static final String HIDDEN_FOLDER_LAUNCH = "hiddenFolderLaunchPosition";
+
+ private static final int REQ_LOCK_PATTERN = 1;
+
+ private String[] mComponentInfo;
+ private String[] mComponentTitles;
+ private boolean mHidden;
+ private PackageManager mPackageManager;
+ private AppsAdapter mAppsAdapter;
+ private ArrayList<AppEntry> mAppEntries;
+
+ private EditText mFolderName;
+ private ListView mListView;
+
+ private Launcher mLauncher;
+
+ private boolean mAuth = false;
+ private boolean mSent = false;
+
+ private OnClickListener mClicklistener = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mHidden = !mHidden;
+
+ ImageView mLock = (ImageView) v;
+ Drawable mLockIcon = mHidden ? getResources().getDrawable(R.drawable.folder_lock_light)
+ : getResources().getDrawable(R.drawable.folder_unlock);
+ mLock.setImageDrawable(mLockIcon);
+ }
+ };
+
+ @Override
+ public View onCreateView (LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View v = inflater.inflate(R.layout.hidden_folder, container, false);
+
+ mLauncher = (Launcher) getActivity();
+ mPackageManager = mLauncher.getPackageManager();
+
+ mHidden = getArguments().getBoolean(HIDDEN_FOLDER_STATUS);
+ Folder folder = mLauncher.mHiddenFolderIcon.getFolder();
+ mComponentInfo = folder.getComponents();
+ mComponentTitles = folder.getComponentTitles();
+ String title = mLauncher.mHiddenFolderIcon.getFolderInfo().title.toString();
+
+ mFolderName = (EditText) v.findViewById(R.id.folder_name);
+ mFolderName.setText(title);
+ mFolderName.setSelectAllOnFocus(true);
+ mFolderName.setInputType(mFolderName.getInputType() |
+ InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_FLAG_CAP_WORDS);
+ mFolderName.setImeOptions(EditorInfo.IME_ACTION_DONE);
+ mFolderName.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+ @Override
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ if (actionId == EditorInfo.IME_ACTION_DONE) {
+ doneEditingText(v);
+ return true;
+ }
+ return false;
+ }
+ });
+ mFolderName.setOnFocusChangeListener(new View.OnFocusChangeListener() {
+ @Override
+ public void onFocusChange(View v, boolean hasFocus) {
+ if (!hasFocus) {
+ doneEditingText(v);
+ }
+ }
+ });
+
+ ImageView mLock = (ImageView) v.findViewById(R.id.folder_lock_icon);
+ Drawable mLockIcon = mHidden ? getResources().getDrawable(R.drawable.folder_lock_light)
+ : getResources().getDrawable(R.drawable.folder_unlock);
+ mLock.setImageDrawable(mLockIcon);
+ mLock.setOnClickListener(mClicklistener);
+
+ mAppsAdapter = new AppsAdapter(mLauncher, R.layout.hidden_apps_list_item);
+ mAppsAdapter.setNotifyOnChange(true);
+ mAppEntries = loadApps();
+ mAppsAdapter.clear();
+ mAppsAdapter.addAll(mAppEntries);
+
+ mListView = (ListView) v.findViewById(R.id.hidden_apps_list);
+ mListView.setAdapter(mAppsAdapter);
+
+ return v;
+ }
+
+ private void doneEditingText(View v) {
+ InputMethodManager mInputMethodManager = (InputMethodManager)
+ mLauncher.getSystemService(Context.INPUT_METHOD_SERVICE);
+ mInputMethodManager.hideSoftInputFromWindow(v.getWindowToken(), 0);
+
+ mListView.requestFocus();
+ }
+
+ private ArrayList<AppEntry> loadApps() {
+ ArrayList<AppEntry> apps = new ArrayList<AppEntry>();
+ int size = mComponentInfo.length;
+ for (int i = 0; i < size; i++) {
+ apps.add(new AppEntry(mComponentInfo[i], mComponentTitles[i]));
+ }
+ return apps;
+ }
+
+ private void removeComponentFromFolder(AppEntry app) {
+ mLauncher.mHiddenFolderIcon.getFolderInfo().remove(
+ mLauncher.mHiddenFolderIcon.getFolder()
+ .getShortcutForComponent(app.componentName));
+
+ mAppEntries.remove(app);
+ mAppsAdapter.remove(app);
+ mAppsAdapter.notifyDataSetInvalidated();
+ }
+
+ public void saveHiddenFolderStatus(int position) {
+ String newTitle = mFolderName.getText().toString();
+ if (mLauncher.mHiddenFolderIcon != null) {
+ if (position != -1) {
+ Folder folder = mLauncher.mHiddenFolderIcon.getFolder();
+ View v = folder.getViewFromPosition(position);
+ Object tag = v.getTag();
+ if (tag instanceof ShortcutInfo) {
+ mLauncher.startActivitySafely(v,
+ ((ShortcutInfo) tag).getIntent(),
+ v.getTag());
+
+ return;
+ }
+ }
+
+ // Folder name
+ FolderInfo info = mLauncher.mHiddenFolderIcon.getFolderInfo();
+ if (!info.title.equals(newTitle)) {
+ info.setTitle(newTitle);
+ mLauncher.mHiddenFolderIcon.getFolder().setFolderName();
+ LauncherModel.updateItemInDatabase(mLauncher, info);
+ }
+
+ // Folder hidden status
+ if (info.hidden == mHidden) {
+ return;
+ } else {
+ info.hidden = mHidden;
+ // flip the boolean value to accomodate framework
+ // in framework "false" is "protected" and "true" is "visible"
+ mLauncher.mHiddenFolderIcon.getFolder().modifyProtectedApps(!info.hidden);
+
+ LauncherModel.updateItemInDatabase(mLauncher, info);
+ // We need to make sure this change gets written to the DB before
+ // OnResume restarts the process
+ mLauncher.mModel.flushWorkerThread();
+ }
+ }
+
+ FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
+ fragmentTransaction
+ .remove(mLauncher.mHiddenFolderFragment).commit();
+ }
+
+ public class AppsAdapter extends ArrayAdapter<AppEntry> {
+
+ private final LayoutInflater mInflator;
+
+ private ConcurrentHashMap<String, Drawable> mIcons;
+ private Drawable mDefaultImg;
+ private List<AppEntry> mApps;
+
+ public AppsAdapter(Context context, int textViewResourceId) {
+ super(context, textViewResourceId);
+
+ mApps = new ArrayList<AppEntry>();
+
+ mInflator = LayoutInflater.from(context);
+
+ // set the default icon till the actual app icon is loaded in async
+ // task
+ mDefaultImg = context.getResources().getDrawable(
+ android.R.mipmap.sym_def_app_icon);
+ mIcons = new ConcurrentHashMap<String, Drawable>();
+ }
+
+ @Override
+ public View getView(final int position, View convertView,
+ ViewGroup parent) {
+ final AppViewHolder viewHolder;
+
+ if (convertView == null) {
+ convertView = mInflator.inflate(
+ R.layout.hidden_folder_apps_list_item, parent, false);
+ viewHolder = new AppViewHolder(convertView, position);
+ convertView.setTag(viewHolder);
+ } else {
+ viewHolder = (AppViewHolder) convertView.getTag();
+ }
+
+ final AppEntry app = getItem(position);
+
+ viewHolder.title.setText(app.title);
+
+ Drawable icon = mIcons.get(app.componentName.getPackageName());
+ viewHolder.icon.setImageDrawable(icon != null ? icon : mDefaultImg);
+ viewHolder.remove.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ removeComponentFromFolder(app);
+ }
+ });
+
+ convertView.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ saveHiddenFolderStatus(viewHolder.position);
+ }
+ });
+
+ return convertView;
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ @Override
+ public void notifyDataSetChanged() {
+ super.notifyDataSetChanged();
+ // If we have new items, we have to load their icons
+ // If items were deleted, remove them from our mApps
+ List<AppEntry> newApps = new ArrayList<AppEntry>(getCount());
+ List<AppEntry> oldApps = new ArrayList<AppEntry>(getCount());
+ for (int i = 0; i < getCount(); i++) {
+ AppEntry app = getItem(i);
+ if (mApps.contains(app)) {
+ oldApps.add(app);
+ } else {
+ newApps.add(app);
+ }
+ }
+
+ if (newApps.size() > 0) {
+ new LoadIconsTask().execute(newApps.toArray(new AppEntry[] {}));
+ newApps.addAll(oldApps);
+ mApps = newApps;
+ } else {
+ mApps = oldApps;
+ }
+ }
+
+ /**
+ * An asynchronous task to load the icons of the installed applications.
+ */
+ private class LoadIconsTask extends AsyncTask<AppEntry, Void, Void> {
+ @Override
+ protected Void doInBackground(AppEntry... apps) {
+ for (AppEntry app : apps) {
+ try {
+ if (mIcons.containsKey(app.componentName
+ .getPackageName())) {
+ continue;
+ }
+ Drawable icon = mPackageManager
+ .getApplicationIcon(app.componentName
+ .getPackageName());
+ mIcons.put(app.componentName.getPackageName(), icon);
+ publishProgress();
+ } catch (PackageManager.NameNotFoundException e) {
+ // ignored; app will show up with default image
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ protected void onProgressUpdate(Void... progress) {
+ notifyDataSetChanged();
+ }
+ }
+ }
+
+ private final class AppEntry {
+
+ public final ComponentName componentName;
+ public final String title;
+
+ public AppEntry(String component, String title) {
+ componentName = ComponentName.unflattenFromString(component);
+ this.title = title;
+ }
+ }
+
+ private static class AppViewHolder {
+ public final TextView title;
+ public final ImageView icon;
+ public final ImageView remove;
+ public final int position;
+
+ public AppViewHolder(View parentView, int position) {
+ icon = (ImageView) parentView.findViewById(R.id.icon);
+ remove = (ImageView) parentView.findViewById(R.id.remove);
+ title = (TextView) parentView.findViewById(R.id.title);
+ this.position = position;
+ }
+ }
+}
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 2812b4129..0cca03b8c 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -149,7 +149,7 @@ public class Hotseat extends FrameLayout {
void addAllAppsFolder(IconCache iconCache,
ArrayList<AppInfo> allApps, ArrayList<ComponentName> onWorkspace,
Launcher launcher, Workspace workspace) {
- if (AppsCustomizePagedView.DISABLE_ALL_APPS) {
+ if (LauncherAppState.isDisableAllApps()) {
FolderInfo fi = new FolderInfo();
fi.cellX = getCellXFromOrder(mAllAppsButtonRank);
@@ -179,7 +179,7 @@ public class Hotseat extends FrameLayout {
}
void addAppsToAllAppsFolder(ArrayList<AppInfo> apps) {
- if (AppsCustomizePagedView.DISABLE_ALL_APPS) {
+ if (LauncherAppState.isDisableAllApps()) {
View v = mContent.getChildAt(getCellXFromOrder(mAllAppsButtonRank), getCellYFromOrder(mAllAppsButtonRank));
FolderIcon fi = null;
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index e1659e795..1dade8027 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -16,6 +16,8 @@
package com.android.launcher3;
+import com.android.launcher3.backup.BackupProtos;
+
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
@@ -25,16 +27,24 @@ import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
import android.graphics.Canvas;
+import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
-
+import android.util.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.Map.Entry;
-import com.android.launcher3.settings.SettingsProvider;
-
/**
* Cache of application icons. Icons can be made from any thread.
*/
@@ -43,7 +53,9 @@ public class IconCache {
private static final String TAG = "Launcher.IconCache";
private static final int INITIAL_ICON_CACHE_CAPACITY = 50;
- private IconPackHelper mIconPackHelper;
+ private static final String RESOURCE_FILE_PREFIX = "icon_";
+
+ private static final boolean DEBUG = true;
private static class CacheEntry {
public Bitmap icon;
@@ -67,9 +79,6 @@ public class IconCache {
// need to set mIconDpi before getting default icon
mDefaultIcon = makeDefaultIcon();
-
- mIconPackHelper = new IconPackHelper(context);
- loadIconPack();
}
public Drawable getFullResDefaultActivityIcon() {
@@ -117,18 +126,12 @@ public class IconCache {
resources = null;
}
if (resources != null) {
- int iconId = 0;
- if (mIconPackHelper != null && mIconPackHelper.isIconPackLoaded()) {
- iconId = mIconPackHelper.getResourceIdForActivityIcon(info);
- if (iconId != 0) {
- return getFullResIcon(mIconPackHelper.getIconPackResources(), iconId);
- }
- }
- iconId = info.getIconResource();
+ int iconId = info.getIconResource();
if (iconId != 0) {
return getFullResIcon(resources, iconId);
}
}
+
return getFullResDefaultActivityIcon();
}
@@ -144,16 +147,6 @@ public class IconCache {
return b;
}
- private void loadIconPack() {
- mIconPackHelper.unloadIconPack();
- String iconPack = SettingsProvider.getStringCustomDefault(mContext,
- SettingsProvider.SETTINGS_UI_GENERAL_ICONS_ICON_PACK, "");
- if (!TextUtils.isEmpty(iconPack) && !mIconPackHelper.loadIconPack(iconPack)) {
- SettingsProvider.putString(mContext,
- SettingsProvider.SETTINGS_UI_GENERAL_ICONS_ICON_PACK, "");
- }
- }
-
/**
* Remove any records for the supplied ComponentName.
*/
@@ -164,13 +157,27 @@ public class IconCache {
}
/**
+ * Remove any records for the supplied package name.
+ */
+ public void remove(String packageName) {
+ HashSet<ComponentName> forDeletion = new HashSet<ComponentName>();
+ for (ComponentName componentName: mCache.keySet()) {
+ if (componentName.getPackageName().equals(packageName)) {
+ forDeletion.add(componentName);
+ }
+ }
+ for (ComponentName condemned: forDeletion) {
+ remove(condemned);
+ }
+ }
+
+ /**
* Empty out the cache.
*/
public void flush() {
synchronized (mCache) {
mCache.clear();
}
- loadIconPack();
}
/**
@@ -181,7 +188,7 @@ public class IconCache {
Iterator<Entry<ComponentName, CacheEntry>> it = mCache.entrySet().iterator();
while (it.hasNext()) {
final CacheEntry e = it.next().getValue();
- if (e.icon.getWidth() != grid.iconSizePx || e.icon.getHeight() != grid.iconSizePx) {
+ if (e.icon.getWidth() < grid.iconSizePx || e.icon.getHeight() < grid.iconSizePx) {
it.remove();
}
}
@@ -202,15 +209,22 @@ public class IconCache {
}
public Bitmap getIcon(Intent intent) {
+ return getIcon(intent, null);
+ }
+
+ public Bitmap getIcon(Intent intent, String title) {
synchronized (mCache) {
final ResolveInfo resolveInfo = mPackageManager.resolveActivity(intent, 0);
ComponentName component = intent.getComponent();
- if (resolveInfo == null || component == null) {
+ if (component == null) {
return mDefaultIcon;
}
CacheEntry entry = cacheLocked(component, resolveInfo, null);
+ if (title != null) {
+ entry.title = title;
+ }
return entry.icon;
}
}
@@ -239,28 +253,34 @@ public class IconCache {
mCache.put(componentName, entry);
- ComponentName key = LauncherModel.getComponentNameFromResolveInfo(info);
- if (labelCache != null && labelCache.containsKey(key)) {
- entry.title = labelCache.get(key).toString();
- } else {
- entry.title = info.loadLabel(mPackageManager).toString();
- if (labelCache != null) {
- labelCache.put(key, entry.title);
+ if (info != null) {
+ ComponentName key = LauncherModel.getComponentNameFromResolveInfo(info);
+ if (labelCache != null && labelCache.containsKey(key)) {
+ entry.title = labelCache.get(key).toString();
+ } else {
+ entry.title = info.loadLabel(mPackageManager).toString();
+ if (labelCache != null) {
+ labelCache.put(key, entry.title);
+ }
+ }
+ if (entry.title == null) {
+ entry.title = info.activityInfo.name;
}
- }
- if (entry.title == null) {
- entry.title = info.activityInfo.name;
- }
- Drawable icon = getFullResIcon(info);
- if (mIconPackHelper.isIconPackLoaded() && (mIconPackHelper
- .getResourceIdForActivityIcon(info.activityInfo) == 0)) {
entry.icon = Utilities.createIconBitmap(
- icon, mContext, mIconPackHelper.getIconBack(),
- mIconPackHelper.getIconMask(), mIconPackHelper.getIconUpon(), mIconPackHelper.getIconScale());
+ getFullResIcon(info), mContext);
} else {
- entry.icon = Utilities.createIconBitmap(
- icon, mContext);
+ entry.title = "";
+ Bitmap preloaded = getPreloadedIcon(componentName);
+ if (preloaded != null) {
+ if (DEBUG) Log.d(TAG, "using preloaded icon for " +
+ componentName.toShortString());
+ entry.icon = preloaded;
+ } else {
+ if (DEBUG) Log.d(TAG, "using default icon for " +
+ componentName.toShortString());
+ entry.icon = mDefaultIcon;
+ }
}
}
return entry;
@@ -276,4 +296,137 @@ public class IconCache {
return set;
}
}
+
+ /**
+ * Pre-load an icon into the persistent cache.
+ *
+ * <P>Queries for a component that does not exist in the package manager
+ * will be answered by the persistent cache.
+ *
+ * @param context application context
+ * @param componentName the icon should be returned for this component
+ * @param icon the icon to be persisted
+ * @param dpi the native density of the icon
+ */
+ public static void preloadIcon(Context context, ComponentName componentName, Bitmap icon,
+ int dpi) {
+ // TODO rescale to the correct native DPI
+ try {
+ PackageManager packageManager = context.getPackageManager();
+ packageManager.getActivityIcon(componentName);
+ // component is present on the system already, do nothing
+ return;
+ } catch (PackageManager.NameNotFoundException e) {
+ // pass
+ }
+
+ final String key = componentName.flattenToString();
+ FileOutputStream resourceFile = null;
+ try {
+ resourceFile = context.openFileOutput(getResourceFilename(componentName),
+ Context.MODE_PRIVATE);
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ if (icon.compress(android.graphics.Bitmap.CompressFormat.PNG, 75, os)) {
+ byte[] buffer = os.toByteArray();
+ resourceFile.write(buffer, 0, buffer.length);
+ } else {
+ Log.w(TAG, "failed to encode cache for " + key);
+ return;
+ }
+ } catch (FileNotFoundException e) {
+ Log.w(TAG, "failed to pre-load cache for " + key, e);
+ } catch (IOException e) {
+ Log.w(TAG, "failed to pre-load cache for " + key, e);
+ } finally {
+ if (resourceFile != null) {
+ try {
+ resourceFile.close();
+ } catch (IOException e) {
+ Log.d(TAG, "failed to save restored icon for: " + key, e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Read a pre-loaded icon from the persistent icon cache.
+ *
+ * @param componentName the component that should own the icon
+ * @returns a bitmap if one is cached, or null.
+ */
+ private Bitmap getPreloadedIcon(ComponentName componentName) {
+ final String key = componentName.flattenToShortString();
+
+ if (DEBUG) Log.v(TAG, "looking for pre-load icon for " + key);
+ Bitmap icon = null;
+ FileInputStream resourceFile = null;
+ try {
+ resourceFile = mContext.openFileInput(getResourceFilename(componentName));
+ byte[] buffer = new byte[1024];
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+ int bytesRead = 0;
+ while(bytesRead >= 0) {
+ bytes.write(buffer, 0, bytesRead);
+ bytesRead = resourceFile.read(buffer, 0, buffer.length);
+ }
+ if (DEBUG) Log.d(TAG, "read " + bytes.size());
+ icon = BitmapFactory.decodeByteArray(bytes.toByteArray(), 0, bytes.size());
+ if (icon == null) {
+ Log.w(TAG, "failed to decode pre-load icon for " + key);
+ }
+ } catch (FileNotFoundException e) {
+ if (DEBUG) Log.d(TAG, "there is no restored icon for: " + key, e);
+ } catch (IOException e) {
+ Log.w(TAG, "failed to read pre-load icon for: " + key, e);
+ } finally {
+ if(resourceFile != null) {
+ try {
+ resourceFile.close();
+ } catch (IOException e) {
+ Log.d(TAG, "failed to manage pre-load icon file: " + key, e);
+ }
+ }
+ }
+
+ if (icon != null) {
+ // TODO: handle alpha mask in the view layer
+ Bitmap b = Bitmap.createBitmap(Math.max(icon.getWidth(), 1),
+ Math.max(icon.getHeight(), 1),
+ Bitmap.Config.ARGB_8888);
+ Canvas c = new Canvas(b);
+ Paint paint = new Paint();
+ paint.setAlpha(127);
+ c.drawBitmap(icon, 0, 0, paint);
+ c.setBitmap(null);
+ icon.recycle();
+ icon = b;
+ }
+
+ return icon;
+ }
+
+ /**
+ * Remove a pre-loaded icon from the persistent icon cache.
+ *
+ * @param componentName the component that should own the icon
+ * @returns true on success
+ */
+ public boolean deletePreloadedIcon(ComponentName componentName) {
+ if (componentName == null) {
+ return false;
+ }
+ if (mCache.remove(componentName) != null) {
+ if (DEBUG) Log.d(TAG, "removed pre-loaded icon from the in-memory cache");
+ }
+ boolean success = mContext.deleteFile(getResourceFilename(componentName));
+ if (DEBUG && success) Log.d(TAG, "removed pre-loaded icon from persistent cache");
+
+ return success;
+ }
+
+ private static String getResourceFilename(ComponentName component) {
+ String resourceName = component.flattenToShortString();
+ String filename = resourceName.replace(File.separatorChar, '_');
+ return RESOURCE_FILE_PREFIX + filename;
+ }
}
diff --git a/src/com/android/launcher3/IconPackHelper.java b/src/com/android/launcher3/IconPackHelper.java
deleted file mode 100644
index 69fadd965..000000000
--- a/src/com/android/launcher3/IconPackHelper.java
+++ /dev/null
@@ -1,517 +0,0 @@
-package com.android.launcher3;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.reflect.Field;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.Map;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlPullParserFactory;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
-import android.content.res.XmlResourceParser;
-import android.graphics.drawable.Drawable;
-import android.text.TextUtils;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-import android.widget.ImageView;
-import android.widget.RadioButton;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.settings.SettingsProvider;
-
-public class IconPackHelper {
-
- static final String ICON_MASK_TAG = "iconmask";
- static final String ICON_BACK_TAG = "iconback";
- static final String ICON_UPON_TAG = "iconupon";
- static final String ICON_SCALE_TAG = "scale";
-
- public final static String[] sSupportedActions = new String[] {
- "org.adw.launcher.THEMES",
- "com.gau.go.launcherex.theme"
- };
-
- public static final String[] sSupportedCategories = new String[] {
- "com.fede.launcher.THEME_ICONPACK",
- "com.anddoes.launcher.THEME",
- "com.teslacoilsw.launcher.THEME"
- };
-
- // Holds package/class -> drawable
- private Map<String, String> mIconPackResources;
- private final Context mContext;
- private String mLoadedIconPackName;
- private Resources mLoadedIconPackResource;
- private Drawable mIconBack, mIconUpon, mIconMask;
- private float mIconScale;
-
- public Drawable getIconBack() {
- return mIconBack;
- }
-
- public Drawable getIconMask() {
- return mIconMask;
- }
-
- public Drawable getIconUpon() {
- return mIconUpon;
- }
-
- public float getIconScale() {
- return mIconScale;
- }
-
- IconPackHelper(Context context) {
- mContext = context;
- mIconPackResources = new HashMap<String, String>();
- }
-
- private Drawable getDrawableForName(String name) {
- if (isIconPackLoaded()) {
- String item = mIconPackResources.get(name);
- if (!TextUtils.isEmpty(item)) {
- int id = getResourceIdForDrawable(item);
- if (id != 0) {
- return mLoadedIconPackResource.getDrawable(id);
- }
- }
- }
- return null;
- }
-
- public static Map<String, IconPackInfo> getSupportedPackages(Context context) {
- Intent i = new Intent();
- Map<String, IconPackInfo> packages = new HashMap<String, IconPackInfo>();
- PackageManager packageManager = context.getPackageManager();
- for (String action : sSupportedActions) {
- i.setAction(action);
- for (ResolveInfo r : packageManager.queryIntentActivities(i, 0)) {
- IconPackInfo info = new IconPackInfo(r, packageManager);
- packages.put(r.activityInfo.packageName, info);
- }
- }
- i = new Intent(Intent.ACTION_MAIN);
- for (String category : sSupportedCategories) {
- i.addCategory(category);
- for (ResolveInfo r : packageManager.queryIntentActivities(i, 0)) {
- IconPackInfo info = new IconPackInfo(r, packageManager);
- packages.put(r.activityInfo.packageName, info);
- }
- i.removeCategory(category);
- }
- return packages;
- }
-
- private static void loadResourcesFromXmlParser(XmlPullParser parser,
- Map<String, String> iconPackResources) throws XmlPullParserException, IOException {
- int eventType = parser.getEventType();
- do {
-
- if (eventType != XmlPullParser.START_TAG) {
- continue;
- }
-
- if (parser.getName().equalsIgnoreCase(ICON_MASK_TAG) ||
- parser.getName().equalsIgnoreCase(ICON_BACK_TAG) ||
- parser.getName().equalsIgnoreCase(ICON_UPON_TAG)) {
- String icon = parser.getAttributeValue(null, "img");
- if (icon == null) {
- if (parser.getAttributeCount() == 1) {
- icon = parser.getAttributeValue(0);
- }
- }
- iconPackResources.put(parser.getName().toLowerCase(), icon);
- continue;
- }
-
- if (parser.getName().equalsIgnoreCase(ICON_SCALE_TAG)) {
- String factor = parser.getAttributeValue(null, "factor");
- if (factor == null) {
- if (parser.getAttributeCount() == 1) {
- factor = parser.getAttributeValue(0);
- }
- }
- iconPackResources.put(parser.getName().toLowerCase(), factor);
- continue;
- }
-
- if (!parser.getName().equalsIgnoreCase("item")) {
- continue;
- }
-
- String component = parser.getAttributeValue(null, "component");
- String drawable = parser.getAttributeValue(null, "drawable");
-
- // Validate component/drawable exist
- if (TextUtils.isEmpty(component) || TextUtils.isEmpty(drawable)) {
- continue;
- }
-
- // Validate format/length of component
- if (!component.startsWith("ComponentInfo{") || !component.endsWith("}")
- || component.length() < 16) {
- continue;
- }
-
- // Sanitize stored value
- component = component.substring(14, component.length() - 1).toLowerCase();
-
- ComponentName name = null;
- if (!component.contains("/")) {
- // Package icon reference
- iconPackResources.put(component, drawable);
- } else {
- name = ComponentName.unflattenFromString(component);
- if (name != null) {
- iconPackResources.put(name.getPackageName(), drawable);
- iconPackResources.put(name.getPackageName() + "." + name.getClassName(), drawable);
- }
- }
- } while ((eventType = parser.next()) != XmlPullParser.END_DOCUMENT);
- }
-
- private static void loadApplicationResources(Context context,
- Map<String, String> iconPackResources, String packageName) {
- Field[] drawableItems = null;
- try {
- Context appContext = context.createPackageContext(packageName,
- Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
- drawableItems = Class.forName(packageName+".R$drawable",
- true, appContext.getClassLoader()).getFields();
- } catch (Exception e){
- return;
- }
-
- for (Field f : drawableItems) {
- String name = f.getName();
-
- String icon = name.toLowerCase();
- name = name.replaceAll("_", ".");
-
- iconPackResources.put(name, icon);
-
- int activityIndex = name.lastIndexOf(".");
- if (activityIndex <= 0 || activityIndex == name.length() - 1) {
- continue;
- }
-
- String iconPackage = name.substring(0, activityIndex);
- if (TextUtils.isEmpty(iconPackage)) {
- continue;
- }
- iconPackResources.put(iconPackage, icon);
-
- String iconActivity = name.substring(activityIndex + 1);
- if (TextUtils.isEmpty(iconActivity)) {
- continue;
- }
- iconPackResources.put(iconPackage + "." + iconActivity, icon);
- }
- }
-
- public boolean loadIconPack(String packageName) {
- mIconPackResources = getIconPackResources(mContext, packageName);
- Resources res = null;
- try {
- res = mContext.getPackageManager().getResourcesForApplication(packageName);
- } catch (PackageManager.NameNotFoundException e) {
- e.printStackTrace();
- return false;
- }
- mLoadedIconPackResource = res;
- mLoadedIconPackName = packageName;
- mIconBack = getDrawableForName(ICON_BACK_TAG);
- mIconMask = getDrawableForName(ICON_MASK_TAG);
- mIconUpon = getDrawableForName(ICON_UPON_TAG);
- String scale = mIconPackResources.get(ICON_SCALE_TAG);
- if (scale != null) {
- try {
- mIconScale = Float.valueOf(scale);
- } catch (NumberFormatException e) {
- }
- }
- return true;
- }
-
- public static Map<String, String> getIconPackResources(Context context, String packageName) {
- if (TextUtils.isEmpty(packageName)) {
- return null;
- }
-
- Resources res = null;
- try {
- res = context.getPackageManager().getResourcesForApplication(packageName);
- } catch (PackageManager.NameNotFoundException e) {
- e.printStackTrace();
- return null;
- }
-
- XmlPullParser parser = null;
- InputStream inputStream = null;
- Map<String, String> iconPackResources = new HashMap<String, String>();
-
- try {
- inputStream = res.getAssets().open("appfilter.xml");
- XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
- parser = factory.newPullParser();
- parser.setInput(inputStream, "UTF-8");
- } catch (Exception e) {
- // Catch any exception since we want to fall back to parsing the xml/
- // resource in all cases
- int resId = res.getIdentifier("appfilter", "xml", packageName);
- if (resId != 0) {
- parser = res.getXml(resId);
- }
- }
-
- if (parser != null) {
- try {
- loadResourcesFromXmlParser(parser, iconPackResources);
- return iconPackResources;
- } catch (XmlPullParserException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- // Cleanup resources
- if (parser instanceof XmlResourceParser) {
- ((XmlResourceParser) parser).close();
- }
- if (inputStream != null) {
- try {
- inputStream.close();
- } catch (IOException e) {
- }
- }
- }
- }
-
- // Application uses a different theme format (most likely launcher pro)
- int arrayId = res.getIdentifier("theme_iconpack", "array", packageName);
- if (arrayId == 0) {
- arrayId = res.getIdentifier("icon_pack", "array", packageName);
- }
-
- if (arrayId != 0) {
- String[] iconPack = res.getStringArray(arrayId);
- for (String entry : iconPack) {
-
- if (TextUtils.isEmpty(entry)) {
- continue;
- }
-
- String icon = entry.toLowerCase();
- entry = entry.replaceAll("_", ".");
-
- iconPackResources.put(entry, icon);
-
- int activityIndex = entry.lastIndexOf(".");
- if (activityIndex <= 0 || activityIndex == entry.length() - 1) {
- continue;
- }
-
- String iconPackage = entry.substring(0, activityIndex);
- if (TextUtils.isEmpty(iconPackage)) {
- continue;
- }
- iconPackResources.put(iconPackage, icon);
-
- String iconActivity = entry.substring(activityIndex + 1);
- if (TextUtils.isEmpty(iconActivity)) {
- continue;
- }
- iconPackResources.put(iconPackage + "." + iconActivity, icon);
- }
- } else {
- loadApplicationResources(context, iconPackResources, packageName);
- }
- return iconPackResources;
- }
-
- public void unloadIconPack() {
- mLoadedIconPackResource = null;
- mLoadedIconPackName = null;
- mIconPackResources = null;
- mIconMask = null;
- mIconBack = null;
- mIconUpon = null;
- mIconScale = 1f;
- }
-
- public static void pickIconPack(final Context context, final boolean pickIcon) {
- Map<String, IconPackInfo> supportedPackages = getSupportedPackages(context);
- if (supportedPackages.isEmpty()) {
- Toast.makeText(context, R.string.no_iconpacks_summary, Toast.LENGTH_SHORT).show();
- return;
- }
-
- final IconAdapter adapter = new IconAdapter(context, supportedPackages);
- AlertDialog.Builder builder = new AlertDialog.Builder(context);
- builder.setTitle(R.string.dialog_pick_iconpack_title);
- if (!pickIcon) {
- builder.setAdapter(adapter, new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int position) {
- if (adapter.isCurrentIconPack(position)) {
- ((Launcher) context).getWorkspace().exitOverviewMode(true);
- return;
- }
- String selectedPackage = adapter.getItem(position);
- SettingsProvider.putString(context,
- SettingsProvider.SETTINGS_UI_GENERAL_ICONS_ICON_PACK, selectedPackage);
- LauncherAppState.getInstance().getIconCache().flush();
- LauncherAppState.getInstance().getModel().forceReload();
- ((Launcher) context).getWorkspace().exitOverviewMode(true);
- }
- });
- } else {
- builder.setAdapter(adapter, new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- String selectedPackage = adapter.getItem(which);
- Launcher launcherActivity = (Launcher) context;
- if (TextUtils.isEmpty(selectedPackage)) {
- launcherActivity.onActivityResult(Launcher.REQUEST_PICK_ICON, Activity.RESULT_OK, null);
- } else {
- Intent i = new Intent();
- i.setClass(context, IconPickerActivity.class);
- i.putExtra(IconPickerActivity.PACKAGE_NAME_EXTRA, selectedPackage);
- launcherActivity.startActivityForResult(i, Launcher.REQUEST_PICK_ICON);
- }
- }
- });
- }
- builder.show().getWindow().getDecorView().setAlpha(0.8f);
- }
-
- boolean isIconPackLoaded() {
- return mLoadedIconPackResource != null &&
- mLoadedIconPackName != null &&
- mIconPackResources != null;
- }
-
- private int getResourceIdForDrawable(String resource) {
- int resId = mLoadedIconPackResource.getIdentifier(resource, "drawable", mLoadedIconPackName);
- return resId;
- }
-
- public Resources getIconPackResources() {
- return mLoadedIconPackResource;
- }
-
- public int getResourceIdForActivityIcon(ActivityInfo info) {
- String drawable = mIconPackResources.get(info.packageName.toLowerCase()
- + "." + info.name.toLowerCase());
- if (drawable == null) {
- // Icon pack doesn't have an icon for the activity, fallback to package icon
- drawable = mIconPackResources.get(info.packageName.toLowerCase());
- if (drawable == null) {
- return 0;
- }
- }
- return getResourceIdForDrawable(drawable);
- }
-
- static class IconPackInfo {
- String packageName;
- CharSequence label;
- Drawable icon;
-
- IconPackInfo(ResolveInfo r, PackageManager packageManager) {
- packageName = r.activityInfo.packageName;
- icon = r.loadIcon(packageManager);
- label = r.loadLabel(packageManager);
- }
-
- IconPackInfo(){
- }
-
- public IconPackInfo(String label, Drawable icon, String packageName) {
- this.label = label;
- this.icon = icon;
- this.packageName = packageName;
- }
- }
-
- private static class IconAdapter extends BaseAdapter {
- ArrayList<IconPackInfo> mSupportedPackages;
- LayoutInflater mLayoutInflater;
- String mCurrentIconPack;
- int mCurrentIconPackPosition = -1;
-
- IconAdapter(Context ctx, Map<String, IconPackInfo> supportedPackages) {
- mLayoutInflater = LayoutInflater.from(ctx);
- mSupportedPackages = new ArrayList<IconPackInfo>(supportedPackages.values());
- Collections.sort(mSupportedPackages, new Comparator<IconPackInfo>() {
- @Override
- public int compare(IconPackInfo lhs, IconPackInfo rhs) {
- return lhs.label.toString().compareToIgnoreCase(rhs.label.toString());
- }
- });
-
- Resources res = ctx.getResources();
- String defaultLabel = res.getString(R.string.default_iconpack_title);
- Drawable icon = res.getDrawable(R.mipmap.ic_launcher_home);
- mSupportedPackages.add(0, new IconPackInfo(defaultLabel, icon, ""));
-
- mCurrentIconPack = SettingsProvider.getStringCustomDefault(ctx,
- SettingsProvider.SETTINGS_UI_GENERAL_ICONS_ICON_PACK, "");
- }
-
- @Override
- public int getCount() {
- return mSupportedPackages.size();
- }
-
- @Override
- public String getItem(int position) {
- return (String) mSupportedPackages.get(position).packageName;
- }
-
- @Override
- public long getItemId(int position) {
- return 0;
- }
-
- public boolean isCurrentIconPack(int position) {
- return mCurrentIconPackPosition == position;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- if (convertView == null) {
- convertView = mLayoutInflater.inflate(R.layout.iconpack_chooser, null);
- }
- IconPackInfo info = mSupportedPackages.get(position);
- TextView txtView = (TextView) convertView.findViewById(R.id.title);
- txtView.setText(info.label);
- ImageView imgView = (ImageView) convertView.findViewById(R.id.icon);
- imgView.setImageDrawable(info.icon);
- RadioButton radioButton = (RadioButton) convertView.findViewById(R.id.radio);
- boolean isCurrentIconPack = info.packageName.equals(mCurrentIconPack);
- radioButton.setChecked(isCurrentIconPack);
- if (isCurrentIconPack) {
- mCurrentIconPackPosition = position;
- }
- return convertView;
- }
- }
-
-} \ No newline at end of file
diff --git a/src/com/android/launcher3/IconPickerActivity.java b/src/com/android/launcher3/IconPickerActivity.java
deleted file mode 100644
index 5f08eda68..000000000
--- a/src/com/android/launcher3/IconPickerActivity.java
+++ /dev/null
@@ -1,152 +0,0 @@
-package com.android.launcher3;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.LinkedHashSet;
-import java.util.Map;
-
-import android.app.Activity;
-import android.app.ActivityManager;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.Resources;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.BaseAdapter;
-import android.widget.GridView;
-import android.widget.ImageView;
-
-public class IconPickerActivity extends Activity {
-
- public static final String SELECTED_RESOURCE_EXTRA = "selected_resource";
- public static final String SELECTED_BITMAP_EXTRA = "bitmap";
- public static final String PACKAGE_NAME_EXTRA = "package";
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- ActivityManager activityManager =
- (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
- int iconSize = activityManager.getLauncherLargeIconSize();
- final String pkgName = getIntent().getStringExtra(PACKAGE_NAME_EXTRA);
-
- GridView gridview = new GridView(this);
- gridview.setNumColumns(GridView.AUTO_FIT);
- gridview.setHorizontalSpacing(40);
- gridview.setVerticalSpacing(40);
- gridview.setPadding(20, 20, 20, 0);
- gridview.setFastScrollEnabled(true);
- gridview.setColumnWidth(iconSize);
- gridview.setStretchMode(GridView.STRETCH_COLUMN_WIDTH);
-
- gridview.setAdapter(new ImageAdapter(this, pkgName));
- gridview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> adapterView, View v, int position, long id) {
- Intent in = new Intent();
- DrawableInfo d = (DrawableInfo) adapterView.getAdapter().getItem(position);
- in.putExtra(SELECTED_RESOURCE_EXTRA, pkgName + "|" + d.resource_name);
- in.putExtra(SELECTED_BITMAP_EXTRA, ((BitmapDrawable)d.drawable.get()).getBitmap());
- setResult(Activity.RESULT_OK, in);
- finish();
- }
- });
- setContentView(gridview);
- }
-
- public class ImageAdapter extends BaseAdapter {
- private Context mContext;
- private Resources mResources;
- private ArrayList<DrawableInfo> mDrawables = new ArrayList<DrawableInfo>();
-
- public class FetchDrawable extends AsyncTask<Integer, Void, Drawable> {
- WeakReference<ImageView> mImageView;
-
- FetchDrawable(ImageView imgView) {
- mImageView = new WeakReference<ImageView>(imgView);
- }
-
- @Override
- protected Drawable doInBackground(Integer... position) {
- DrawableInfo info = getItem(position[0]);
- int itemId = info.resource_id;
- Drawable d = mResources.getDrawable(itemId);
- info.drawable = new WeakReference<Drawable>(d);
- return d;
- }
-
- @Override
- public void onPostExecute(Drawable result) {
- if (mImageView.get() != null) {
- mImageView.get().setImageDrawable(result);
- }
- }
- }
-
- public ImageAdapter(Context c, String pkgName) {
- mContext = c;
- Map<String, String> resources = IconPackHelper.getIconPackResources(c, pkgName);
- try {
- mResources = c.getPackageManager().getResourcesForApplication(pkgName);
- LinkedHashSet<String> drawables = new LinkedHashSet<String>(resources.values());
- for (String s : drawables) {
- int id = mResources.getIdentifier(s, "drawable", pkgName);
- if (id != 0) {
- mDrawables.add(new DrawableInfo(s, id));
- }
- }
- } catch (NameNotFoundException e) {
- }
- }
-
- public int getCount() {
- return mDrawables.size();
- }
-
- public DrawableInfo getItem(int position) {
- return mDrawables.get(position);
- }
-
- public long getItemId(int position) {
- return 0;
- }
-
- public View getView(final int position, View convertView, ViewGroup parent) {
- final ImageView imageView;
- if (convertView == null) {
- imageView = new ImageView(mContext);
- imageView.setLayoutParams(new GridView.LayoutParams(
- GridView.LayoutParams.WRAP_CONTENT, GridView.LayoutParams.WRAP_CONTENT));
- imageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
- } else {
- imageView = (ImageView) convertView;
- Object tag = imageView.getTag();
- if (tag != null && tag instanceof FetchDrawable) {
- ((FetchDrawable) tag).cancel(true);
- }
- }
- FetchDrawable req = new FetchDrawable(imageView);
- imageView.setTag(req);
- req.execute(position);
- return imageView;
- }
- }
-
- private class DrawableInfo {
- WeakReference<Drawable> drawable;
- final String resource_name;
- final int resource_id;
- DrawableInfo(String n, int i) {
- resource_name = n;
- resource_id = i;
- }
- }
-}
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index 7df73b1f6..28cef1346 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -24,17 +24,20 @@ import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
+import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;
import android.widget.Toast;
+import org.json.JSONObject;
+import org.json.JSONStringer;
+import org.json.JSONTokener;
+
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
-import org.json.*;
-
public class InstallShortcutReceiver extends BroadcastReceiver {
private static final String TAG = "InstallShortcutReceiver";
private static final boolean DBG = false;
@@ -108,6 +111,9 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
public static void removeFromInstallQueue(SharedPreferences sharedPrefs,
ArrayList<String> packageNames) {
+ if (packageNames.isEmpty()) {
+ return;
+ }
synchronized(sLock) {
Set<String> strings = sharedPrefs.getStringSet(APPS_PENDING_INSTALL, null);
if (DBG) {
@@ -218,18 +224,11 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
if (intent == null) {
return;
}
+
// This name is only used for comparisons and notifications, so fall back to activity name
// if not supplied
- String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
- if (name == null) {
- try {
- PackageManager pm = context.getPackageManager();
- ActivityInfo info = pm.getActivityInfo(intent.getComponent(), 0);
- name = info.loadLabel(pm).toString();
- } catch (PackageManager.NameNotFoundException nnfe) {
- return;
- }
- }
+ String name = ensureValidName(context, intent,
+ data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME)).toString();
Bitmap icon = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
Intent.ShortcutIconResource iconResource =
data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
@@ -272,6 +271,12 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
//final Intent data = pendingInfo.data;
final Intent intent = pendingInfo.launchIntent;
final String name = pendingInfo.name;
+
+ if (LauncherAppState.isDisableAllApps() && !isValidShortcutLaunchIntent(intent)) {
+ if (DBG) Log.d(TAG, "Ignoring shortcut with launchIntent:" + intent);
+ continue;
+ }
+
final boolean exists = LauncherModel.shortcutExists(context, name, intent);
//final boolean allowDuplicate = data.getBooleanExtra(Launcher.EXTRA_SHORTCUT_DUPLICATE, true);
@@ -299,11 +304,35 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
// Add the new apps to the model and bind them
if (!addShortcuts.isEmpty()) {
LauncherAppState app = LauncherAppState.getInstance();
- app.getModel().addAndBindAddedApps(context, addShortcuts, null);
+ app.getModel().addAndBindAddedWorkspaceApps(context, addShortcuts);
}
}
}
+ /**
+ * Returns true if the intent is a valid launch intent for a shortcut.
+ * This is used to identify shortcuts which are different from the ones exposed by the
+ * applications' manifest file.
+ *
+ * When DISABLE_ALL_APPS is true, shortcuts exposed via the app's manifest should never be
+ * duplicated or removed(unless the app is un-installed).
+ *
+ * @param launchIntent The intent that will be launched when the shortcut is clicked.
+ */
+ static boolean isValidShortcutLaunchIntent(Intent launchIntent) {
+ if (launchIntent != null
+ && Intent.ACTION_MAIN.equals(launchIntent.getAction())
+ && launchIntent.getComponent() != null
+ && launchIntent.getCategories() != null
+ && launchIntent.getCategories().size() == 1
+ && launchIntent.hasCategory(Intent.CATEGORY_LAUNCHER)
+ && launchIntent.getExtras() == null
+ && TextUtils.isEmpty(launchIntent.getDataString())) {
+ return false;
+ }
+ return true;
+ }
+
private static ShortcutInfo getShortcutInfo(Context context, Intent data,
Intent launchIntent) {
if (launchIntent.getAction() == null) {
@@ -315,6 +344,25 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
}
LauncherAppState app = LauncherAppState.getInstance();
- return app.getModel().infoFromShortcutIntent(context, data, null);
+ ShortcutInfo info = app.getModel().infoFromShortcutIntent(context, data, null);
+ info.title = ensureValidName(context, launchIntent, info.title);
+ return info;
+ }
+
+ /**
+ * Ensures that we have a valid, non-null name. If the provided name is null, we will return
+ * the application name instead.
+ */
+ private static CharSequence ensureValidName(Context context, Intent intent, CharSequence name) {
+ if (name == null) {
+ try {
+ PackageManager pm = context.getPackageManager();
+ ActivityInfo info = pm.getActivityInfo(intent.getComponent(), 0);
+ name = info.loadLabel(pm).toString();
+ } catch (PackageManager.NameNotFoundException nnfe) {
+ return "";
+ }
+ }
+ return name;
}
}
diff --git a/src/com/android/launcher3/InstallWidgetReceiver.java b/src/com/android/launcher3/InstallWidgetReceiver.java
index 0ef478074..74b9e3d99 100644
--- a/src/com/android/launcher3/InstallWidgetReceiver.java
+++ b/src/com/android/launcher3/InstallWidgetReceiver.java
@@ -16,8 +16,6 @@
package com.android.launcher3;
-import java.util.List;
-
import android.appwidget.AppWidgetProviderInfo;
import android.content.ClipData;
import android.content.Context;
@@ -33,7 +31,7 @@ import android.widget.ImageView;
import android.widget.ListAdapter;
import android.widget.TextView;
-import com.android.launcher3.R;
+import java.util.List;
/**
diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java
index 8c4cefd5f..3dc92c9c2 100644
--- a/src/com/android/launcher3/ItemInfo.java
+++ b/src/com/android/launcher3/ItemInfo.java
@@ -27,7 +27,7 @@ import java.io.IOException;
/**
* Represents an item in the launcher.
*/
-class ItemInfo {
+public class ItemInfo {
static final int NO_ID = -1;
@@ -122,6 +122,10 @@ class ItemInfo {
throw new RuntimeException("Unexpected Intent");
}
+ protected Intent getRestoredIntent() {
+ throw new RuntimeException("Unexpected Intent");
+ }
+
/**
* Write the fields of this item to the DB
*
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 416c80641..ab3f1daa0 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -17,8 +17,6 @@
package com.android.launcher3;
-import android.accounts.Account;
-import android.accounts.AccountManager;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
@@ -29,8 +27,10 @@ import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityOptions;
-import android.app.AlertDialog;
import android.app.Dialog;
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
import android.app.SearchManager;
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetManager;
@@ -41,13 +41,14 @@ import android.content.ComponentCallbacks2;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
-import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.ContentObserver;
@@ -55,6 +56,7 @@ import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Point;
import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.net.Uri;
@@ -65,7 +67,6 @@ import android.os.Handler;
import android.os.Message;
import android.os.StrictMode;
import android.os.SystemClock;
-import android.provider.Settings;
import android.speech.RecognizerIntent;
import android.text.Selection;
import android.text.SpannableStringBuilder;
@@ -83,14 +84,12 @@ import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.View;
-import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.inputmethod.InputMethodManager;
@@ -101,8 +100,9 @@ import android.widget.PopupMenu;
import android.widget.TextView;
import android.widget.Toast;
+import com.android.launcher.home.Home;
import com.android.launcher3.DropTarget.DragObject;
-import com.android.launcher3.settings.SettingsActivity;
+import com.android.launcher3.PagedView.TransitionEffect;
import com.android.launcher3.settings.SettingsProvider;
import java.io.DataInputStream;
@@ -119,6 +119,8 @@ import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
/**
* Default launcher application.
@@ -129,12 +131,16 @@ public class Launcher extends Activity
static final String TAG = "Launcher";
static final boolean LOGD = false;
+ DeviceProfile mGrid;
+
static final boolean PROFILE_STARTUP = false;
static final boolean DEBUG_WIDGETS = false;
static final boolean DEBUG_STRICT_MODE = false;
static final boolean DEBUG_RESUME_TIME = false;
static final boolean DEBUG_DUMP_LOG = false;
+ static final boolean ENABLE_DEBUG_INTENTS = false; // allow DebugIntents to run
+
private static final int REQUEST_CREATE_SHORTCUT = 1;
private static final int REQUEST_CREATE_APPWIDGET = 5;
private static final int REQUEST_PICK_APPLICATION = 6;
@@ -143,8 +149,11 @@ public class Launcher extends Activity
private static final int REQUEST_PICK_WALLPAPER = 10;
private static final int REQUEST_BIND_APPWIDGET = 11;
+
static final int REQUEST_PICK_ICON = 13;
+ private static final int REQUEST_LOCK_PATTERN = 14;
+
/**
* IntentStarter uses request codes starting with this. This must be greater than all activity
* request codes used internally.
@@ -161,6 +170,7 @@ public class Launcher extends Activity
// adb shell setprop log.tag.PROPERTY_NAME [VERBOSE | SUPPRESS]
static final String FORCE_ENABLE_ROTATION_PROPERTY = "launcher_force_rotate";
static final String DUMP_STATE_PROPERTY = "launcher_dump_state";
+ static final String DISABLE_ALL_APPS_PROPERTY = "launcher_noallapps";
// The Intent extra that defines whether to ignore the launch animation
static final String INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION =
@@ -188,6 +198,13 @@ public class Launcher extends Activity
private static final String RUNTIME_STATE_PENDING_ADD_SPAN_Y = "launcher.add_span_y";
// Type: parcelable
private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_INFO = "launcher.add_widget_info";
+ // Type: parcelable
+ private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_ID = "launcher.add_widget_id";
+ // Type: int[]
+ private static final String RUNTIME_STATE_VIEW_IDS = "launcher.view_ids";
+
+
+ static final String FIRST_RUN_ACTIVITY_DISPLAYED = "launcher.first_run_activity_displayed";
private static final String TOOLBAR_ICON_METADATA_NAME = "com.android.launcher.toolbar_icon";
private static final String TOOLBAR_SEARCH_ICON_METADATA_NAME =
@@ -203,25 +220,31 @@ public class Launcher extends Activity
public static final String SHOW_WEIGHT_WATCHER = "debug.show_mem";
public static final boolean SHOW_WEIGHT_WATCHER_DEFAULT = false;
+ public static final String USER_HAS_MIGRATED = "launcher.user_migrated_from_old_data";
+
/** The different states that Launcher can be in. */
private enum State { NONE, WORKSPACE, APPS_CUSTOMIZE, APPS_CUSTOMIZE_SPRING_LOADED };
private State mState = State.WORKSPACE;
private AnimatorSet mStateAnimation;
static final int APPWIDGET_HOST_ID = 1024;
- private static final int EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT = 300;
- private static final int EXIT_SPRINGLOADED_MODE_LONG_TIMEOUT = 600;
- private static final int SHOW_CLING_DURATION = 250;
- private static final int DISMISS_CLING_DURATION = 200;
+ public static final int EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT = 300;
+ public static final int EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT_FOLDER_CLOSE = 400;
+ private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500;
private static final Object sLock = new Object();
private static int sScreen = DEFAULT_SCREEN;
+ private HashMap<Integer, Integer> mItemIdToViewId = new HashMap<Integer, Integer>();
+ private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);
+
// How long to wait before the new-shortcut animation automatically pans the workspace
private static int NEW_APPS_PAGE_MOVE_DELAY = 500;
private static int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5;
private static int NEW_APPS_ANIMATION_DELAY = 500;
+ private boolean mGelIntegrationEnabled = false;
+
private final BroadcastReceiver mCloseSystemDialogsReceiver
= new CloseSystemDialogsIntentReceiver();
private final ContentObserver mWidgetObserver = new AppWidgetResetObserver();
@@ -230,22 +253,37 @@ public class Launcher extends Activity
private Workspace mWorkspace;
private View mLauncherView;
+ private View mPageIndicators;
private DragLayer mDragLayer;
private DragController mDragController;
private View mWeightWatcher;
+ private TransitionEffectsFragment mTransitionEffectsFragment;
+ private DynamicGridSizeFragment mDynamicGridSizeFragment;
+ private LauncherClings mLauncherClings;
+ protected HiddenFolderFragment mHiddenFolderFragment;
+
private AppWidgetManager mAppWidgetManager;
private LauncherAppWidgetHost mAppWidgetHost;
private ItemInfo mPendingAddInfo = new ItemInfo();
private AppWidgetProviderInfo mPendingAddWidgetInfo;
+ private int mPendingAddWidgetId = -1;
private int[] mTmpAddItemCellCoordinates = new int[2];
private FolderInfo mFolderInfo;
+ protected FolderIcon mHiddenFolderIcon;
+ private boolean mHiddenFolderAuth = false;
+
private Hotseat mHotseat;
+
private View mOverviewPanel;
+ private View mDarkPanel;
+ OverviewSettingsPanel mOverviewSettingsPanel;
+
+// private ViewGroup mOverviewPanel;
private View mAllAppsButton;
@@ -253,7 +291,10 @@ public class Launcher extends Activity
private AppsCustomizeLayout mAppsCustomizeLayout;
private AppsCustomizePagedView mAppsCustomizeContent;
private boolean mAutoAdvanceRunning = false;
+ private View mQsb;
private View mQsbBar;
+ private ImageView mQsbBarSearch;
+ private ImageView mQsbBarVoice;
private Bundle mSavedState;
// We set the state in both onCreate and then onNewIntent in some cases, which causes both
@@ -280,14 +321,12 @@ public class Launcher extends Activity
private Dialog mTransitionEffectDialog;
- private LauncherModel mModel;
+ protected LauncherModel mModel;
private IconCache mIconCache;
private boolean mUserPresent = true;
private boolean mVisible = false;
private boolean mHasFocus = false;
private boolean mAttached = false;
- private static final boolean DISABLE_CLINGS = false;
- private static final boolean DISABLE_CUSTOM_CLINGS = true;
private static LocaleConfiguration sLocaleConfiguration = null;
@@ -315,6 +354,7 @@ public class Launcher extends Activity
private Drawable mWorkspaceBackgroundDrawable;
private final ArrayList<Integer> mSynchronouslyBoundPages = new ArrayList<Integer>();
+ private static final boolean DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE = false;
static final ArrayList<String> sDumpLogs = new ArrayList<String>();
static Date sDateStamp = new Date();
@@ -338,9 +378,6 @@ public class Launcher extends Activity
private BubbleTextView mWaitingForResume;
- private HideFromAccessibilityHelper mHideFromAccessibilityHelper
- = new HideFromAccessibilityHelper();
-
// Preferences
private boolean mHideIconLabels;
@@ -368,10 +405,33 @@ public class Launcher extends Activity
private Stats mStats;
- private static boolean isPropertyEnabled(String propertyName) {
+ public Animator.AnimatorListener mAnimatorListener = new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator arg0) {}
+ @Override
+ public void onAnimationRepeat(Animator arg0) {}
+ @Override
+ public void onAnimationEnd(Animator arg0) {
+ mDarkPanel.setVisibility(View.GONE);
+ }
+ @Override
+ public void onAnimationCancel(Animator arg0) {}
+ };
+
+ static boolean isPropertyEnabled(String propertyName) {
return Log.isLoggable(propertyName, Log.VERBOSE);
}
+ private BroadcastReceiver protectedAppsChangedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // Update the workspace
+ updateDynamicGrid();
+ mWorkspace.hideOutlines();
+ mSearchDropTargetBar.showSearchBar(false);
+ }
+ };
+
@Override
protected void onCreate(Bundle savedInstanceState) {
if (DEBUG_STRICT_MODE) {
@@ -391,36 +451,14 @@ public class Launcher extends Activity
super.onCreate(savedInstanceState);
- LauncherAppState.setApplicationContext(getApplicationContext());
- LauncherAppState app = LauncherAppState.getInstance();
-
- mHideIconLabels = SettingsProvider.getBoolean(this,
- SettingsProvider.SETTINGS_UI_HOMESCREEN_HIDE_ICON_LABELS,
- R.bool.preferences_interface_homescreen_hide_icon_labels_default);
-
- // Determine the dynamic grid properties
- Point smallestSize = new Point();
- Point largestSize = new Point();
- Point realSize = new Point();
- Display display = getWindowManager().getDefaultDisplay();
- display.getCurrentSizeRange(smallestSize, largestSize);
- display.getRealSize(realSize);
- DisplayMetrics dm = new DisplayMetrics();
- display.getMetrics(dm);
- // Lazy-initialize the dynamic grid
- DeviceProfile grid = app.initDynamicGrid(this,
- Math.min(smallestSize.x, smallestSize.y),
- Math.min(largestSize.x, largestSize.y),
- realSize.x, realSize.y,
- dm.widthPixels, dm.heightPixels);
+ initializeDynamicGrid();
// the LauncherApplication should call this, but in case of Instrumentation it might not be present yet
mSharedPrefs = getSharedPreferences(LauncherAppState.getSharedPreferencesKey(),
Context.MODE_PRIVATE);
- mModel = app.setLauncher(this);
- mIconCache = app.getIconCache();
- mIconCache.flushInvalidIcons(grid);
+
mDragController = new DragController(this);
+ mLauncherClings = new LauncherClings(this);
mInflater = getLayoutInflater();
mStats = new Stats(this);
@@ -445,7 +483,7 @@ public class Launcher extends Activity
setContentView(R.layout.launcher);
setupViews();
- grid.layout(this);
+ mGrid.layout(this);
registerContentObservers();
@@ -454,25 +492,21 @@ public class Launcher extends Activity
mSavedState = savedInstanceState;
restoreState(mSavedState);
- // Update customization drawer _after_ restoring the states
- if (mAppsCustomizeContent != null) {
- mAppsCustomizeContent.onPackagesUpdated(
- LauncherModel.getSortedWidgetsAndShortcuts(this));
- }
+ restoreGelSetting();
if (PROFILE_STARTUP) {
android.os.Debug.stopMethodTracing();
}
if (!mRestoring) {
- if (sPausedFromUserAction) {
+ if (DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE || sPausedFromUserAction) {
// If the user leaves launcher, then we should just load items asynchronously when
// they return.
- mModel.startLoader(true, -1);
+ mModel.startLoader(true, PagedView.INVALID_RESTORE_PAGE);
} else {
// We only load the page synchronously if the user rotates (or triggers a
// configuration change) while launcher is in the foreground
- mModel.startLoader(true, mWorkspace.getCurrentPage());
+ mModel.startLoader(true, mWorkspace.getRestorePage());
}
}
@@ -488,7 +522,60 @@ public class Launcher extends Activity
// On large interfaces, we want the screen to auto-rotate based on the current orientation
unlockScreenOrientation(true);
- showFirstRunCling();
+ // The two first run cling paths are mutually exclusive, if the launcher is preinstalled
+ // on the device, then we always show the first run cling experience (or if there is no
+ // launcher2). Otherwise, we prompt the user upon started for migration
+ showFirstRunActivity();
+ if (mLauncherClings.shouldShowFirstRunOrMigrationClings()) {
+ if (mModel.canMigrateFromOldLauncherDb(this)) {
+ mLauncherClings.showMigrationCling();
+ } else {
+ mLauncherClings.showFirstRunCling();
+ }
+ } else {
+ mLauncherClings.removeFirstRunAndMigrationClings();
+ }
+ IntentFilter protectedAppsFilter = new IntentFilter(
+ "cyanogenmod.intent.action.PROTECTED_COMPONENT_UPDATE");
+ registerReceiver(protectedAppsChangedReceiver, protectedAppsFilter,
+ "cyanogenmod.permission.PROTECTED_APP", null);
+ }
+
+ public void restoreGelSetting() {
+ mGelIntegrationEnabled = SettingsProvider.getBoolean(this,
+ SettingsProvider.SETTINGS_UI_HOMESCREEN_SEARCH_SCREEN_LEFT,
+ R.bool.preferences_interface_homescreen_search_screen_left_default);
+ }
+
+ void initializeDynamicGrid() {
+ LauncherAppState.setApplicationContext(getApplicationContext());
+ LauncherAppState app = LauncherAppState.getInstance();
+
+ mHideIconLabels = SettingsProvider.getBoolean(this,
+ SettingsProvider.SETTINGS_UI_HOMESCREEN_HIDE_ICON_LABELS,
+ R.bool.preferences_interface_homescreen_hide_icon_labels_default);
+
+ restoreGelSetting();
+
+ // Determine the dynamic grid properties
+ Point smallestSize = new Point();
+ Point largestSize = new Point();
+ Point realSize = new Point();
+ Display display = getWindowManager().getDefaultDisplay();
+ display.getCurrentSizeRange(smallestSize, largestSize);
+ display.getRealSize(realSize);
+ DisplayMetrics dm = new DisplayMetrics();
+ display.getMetrics(dm);
+ // Lazy-initialize the dynamic grid
+ mGrid = app.initDynamicGrid(this,
+ Math.min(smallestSize.x, smallestSize.y),
+ Math.min(largestSize.x, largestSize.y),
+ realSize.x, realSize.y,
+ dm.widthPixels, dm.heightPixels);
+
+ mModel = app.setLauncher(this);
+ mIconCache = app.getIconCache();
+ mIconCache.flushInvalidIcons(mGrid);
}
protected void onUserLeaveHint() {
@@ -496,17 +583,64 @@ public class Launcher extends Activity
sPausedFromUserAction = true;
}
- /** To be overriden by subclasses to hint to Launcher that we have custom content */
- protected boolean hasCustomContentToLeft() {
+ /** To be overridden by subclasses to hint to Launcher that we have custom content */
+ protected boolean hasCustomSearchSupport() {
return false;
}
+ protected boolean hasCustomContentToLeft() {
+ return isGelIntegrationSupported() && isGelIntegrationEnabled();
+ }
+
+ public boolean isGelIntegrationSupported() {
+ final SearchManager searchManager =
+ (SearchManager) getSystemService(Context.SEARCH_SERVICE);
+ ComponentName globalSearchActivity = searchManager.getGlobalSearchActivity();
+
+ // Currently the only custom content available is the GEL launcher integration,
+ // only supported on CyanogenMod.
+ return globalSearchActivity != null && isCM();
+ }
+
+ public boolean isGelIntegrationEnabled() {
+ return mGelIntegrationEnabled;
+ }
+
+ public void onCustomContentLaunch() {
+ if(isGelIntegrationEnabled() && isGelIntegrationSupported()) {
+ GelIntegrationHelper.getInstance().registerSwipeBackGestureListenerAndStartGel(this, mWorkspace.isLayoutRtl());
+ }
+ }
+
+ /**
+ * Check if the device running this application is running CyanogenMod.
+ * @return true if this device is running CM.
+ */
+ protected boolean isCM() {
+ return getPackageManager().hasSystemFeature("com.cyanogenmod.android");
+ }
+
/**
* To be overridden by subclasses to create the custom content and call
* {@link #addToCustomContentPage}. This will only be invoked if
* {@link #hasCustomContentToLeft()} is {@code true}.
*/
- protected void addCustomContentToLeft() {
+ protected void populateCustomContentContainer() {
+ }
+
+ /**
+ * To be overridden by subclasses to indicate that there is an activity to launch
+ * before showing the standard launcher experience.
+ */
+ protected boolean hasFirstRunActivity() {
+ return false;
+ }
+
+ /**
+ * To be overridden by subclasses to launch any first run activity
+ */
+ protected Intent getFirstRunActivity() {
+ return null;
}
/**
@@ -521,13 +655,22 @@ public class Launcher extends Activity
if (!mWorkspace.hasCustomContent() && hasCustomContentToLeft()) {
// Create the custom content page and call the subclass to populate it.
- mWorkspace.createCustomContentPage();
- addCustomContentToLeft();
+ mWorkspace.createCustomContentContainer();
+ populateCustomContentContainer();
} else if (mWorkspace.hasCustomContent() && !hasCustomContentToLeft()) {
mWorkspace.removeCustomContentPage();
}
}
+ /** To be overriden by subclasses to hint to Launcher that we have custom content and
+ * support {@link #hasCustomSearchSupport()}
+ * @see com.android.launcher.home.Home#MODE_SEARCH_TEXT
+ * @see com.android.launcher.home.Home#MODE_SEARCH_VOICE
+ * */
+ protected void requestSearch(int mode) {
+ // To be implemented
+ }
+
private void updateGlobalIcons() {
boolean searchVisible = false;
boolean voiceVisible = false;
@@ -590,12 +733,12 @@ public class Launcher extends Activity
mIconCache.flush();
final LocaleConfiguration localeConfiguration = sLocaleConfiguration;
- new Thread("WriteLocaleConfiguration") {
- @Override
- public void run() {
+ new AsyncTask<Void, Void, Void>() {
+ public Void doInBackground(Void ... args) {
writeConfiguration(Launcher.this, localeConfiguration);
+ return null;
}
- }.start();
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
}
}
@@ -659,10 +802,6 @@ public class Launcher extends Activity
return mInflater;
}
- public DragLayer getDragLayer() {
- return mDragLayer;
- }
-
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().
@@ -682,6 +821,34 @@ public class Launcher extends Activity
}
/**
+ * Copied from View -- the View version of the method isn't called
+ * anywhere else in our process and only exists for API level 17+,
+ * so it's ok to keep our own version with no API requirement.
+ */
+ public static int generateViewId() {
+ for (;;) {
+ final int result = sNextGeneratedId.get();
+ // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
+ int newValue = result + 1;
+ if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.
+ if (sNextGeneratedId.compareAndSet(result, newValue)) {
+ return result;
+ }
+ }
+ }
+
+ public int getViewIdForItem(ItemInfo info) {
+ // This cast is safe given the > 2B range for int.
+ int itemId = (int) info.id;
+ if (mItemIdToViewId.containsKey(itemId)) {
+ return mItemIdToViewId.get(itemId);
+ }
+ int viewId = generateViewId();
+ mItemIdToViewId.put(itemId, viewId);
+ return viewId;
+ }
+
+ /**
* Returns whether we should delay spring loaded mode -- for shortcuts and widgets that have
* a configuration step, this allows the proper animations to run after other transitions.
*/
@@ -718,14 +885,27 @@ public class Launcher extends Activity
final int requestCode, final int resultCode, final Intent data) {
// Reset the startActivity waiting flag
mWaitingForResult = false;
+ int pendingAddWidgetId = mPendingAddWidgetId;
+ mPendingAddWidgetId = -1;
+
+ Runnable exitSpringLoaded = new Runnable() {
+ @Override
+ public void run() {
+ exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED),
+ EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
+ }
+ };
if (requestCode == REQUEST_BIND_APPWIDGET) {
- int appWidgetId = data != null ?
+ final int appWidgetId = data != null ?
data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1;
if (resultCode == RESULT_CANCELED) {
completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId);
+ mWorkspace.removeExtraEmptyScreen(true, exitSpringLoaded,
+ ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
} else if (resultCode == RESULT_OK) {
- addAppWidgetImpl(appWidgetId, mPendingAddInfo, null, mPendingAddWidgetInfo);
+ addAppWidgetImpl(appWidgetId, mPendingAddInfo, null,
+ mPendingAddWidgetInfo, ON_ACTIVITY_RESULT_ANIMATION_DELAY);
}
return;
} else if (requestCode == REQUEST_PICK_WALLPAPER) {
@@ -733,24 +913,67 @@ public class Launcher extends Activity
mWorkspace.exitOverviewMode(false);
}
return;
+ } else if (requestCode == REQUEST_LOCK_PATTERN) {
+ mHiddenFolderAuth = true;
+ switch (resultCode) {
+ case RESULT_OK:
+ FragmentManager fragmentManager = getFragmentManager();
+ FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
+
+ fragmentTransaction.setCustomAnimations(0, 0);
+ fragmentTransaction.replace(R.id.launcher, mHiddenFolderFragment,
+ HiddenFolderFragment.HIDDEN_FOLDER_FRAGMENT);
+ fragmentTransaction.commit();
+ break;
+ case RESULT_CANCELED:
+ // User failed to enter/confirm a lock pattern, back out
+ break;
+ }
+ return;
}
- boolean delayExitSpringLoadedMode = false;
boolean isWidgetDrop = (requestCode == REQUEST_PICK_APPWIDGET ||
requestCode == REQUEST_CREATE_APPWIDGET);
// We have special handling for widgets
if (isWidgetDrop) {
- int appWidgetId = data != null ?
- data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1;
- if (appWidgetId < 0) {
+ final int appWidgetId;
+ int widgetId = data != null ? data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
+ : -1;
+ if (widgetId < 0) {
+ appWidgetId = pendingAddWidgetId;
+ } else {
+ appWidgetId = widgetId;
+ }
+
+ final int result;
+ final Runnable onComplete;
+ if (appWidgetId < 0 || resultCode == RESULT_CANCELED) {
Log.e(TAG, "Error: appWidgetId (EXTRA_APPWIDGET_ID) was not returned from the \\" +
"widget configuration activity.");
- completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId);
- mWorkspace.stripEmptyScreens();
+ result = RESULT_CANCELED;
+ completeTwoStageWidgetDrop(result, appWidgetId);
+ onComplete = new Runnable() {
+ @Override
+ public void run() {
+ exitSpringLoadedDragModeDelayed(false, 0, null);
+ }
+ };
} else {
- completeTwoStageWidgetDrop(resultCode, appWidgetId);
+ result = resultCode;
+ final CellLayout dropLayout =
+ (CellLayout) mWorkspace.getScreenWithId(mPendingAddInfo.screenId);
+ dropLayout.setDropPending(true);
+ onComplete = new Runnable() {
+ @Override
+ public void run() {
+ completeTwoStageWidgetDrop(result, appWidgetId);
+ dropLayout.setDropPending(false);
+ }
+ };
}
+ mWorkspace.removeExtraEmptyScreen(true, onComplete, ON_ACTIVITY_RESULT_ANIMATION_DELAY,
+ false);
return;
}
@@ -770,15 +993,15 @@ public class Launcher extends Activity
if (isWorkspaceLocked()) {
sPendingAddList.add(args);
} else {
- delayExitSpringLoadedMode = completeAdd(args);
+ completeAdd(args);
}
+ mWorkspace.removeExtraEmptyScreen(true, exitSpringLoaded,
+ ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
} else if (resultCode == RESULT_CANCELED) {
- mWorkspace.stripEmptyScreens();
+ mWorkspace.removeExtraEmptyScreen(true, exitSpringLoaded,
+ ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
}
mDragLayer.clearAnimatedView();
- // Exit spring loaded mode if necessary after cancelling the configuration of a widget
- exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED), delayExitSpringLoadedMode,
- null);
}
private void completeTwoStageWidgetDrop(final int resultCode, final int appWidgetId) {
@@ -798,25 +1021,19 @@ public class Launcher extends Activity
public void run() {
completeAddAppWidget(appWidgetId, mPendingAddInfo.container,
mPendingAddInfo.screenId, layout, null);
- exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED), false,
- null);
+ exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED),
+ EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
}
};
} else if (resultCode == RESULT_CANCELED) {
+ mAppWidgetHost.deleteAppWidgetId(appWidgetId);
animationType = Workspace.CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION;
- onCompleteRunnable = new Runnable() {
- @Override
- public void run() {
- exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED), false,
- null);
- }
- };
}
if (mDragLayer.getAnimatedView() != null) {
mWorkspace.animateWidgetDrop(mPendingAddInfo, cellLayout,
(DragView) mDragLayer.getAnimatedView(), onCompleteRunnable,
animationType, boundWidget, true);
- } else {
+ } else if (onCompleteRunnable != null) {
// The animated view may be null in the case of a rotation during widget configuration
onCompleteRunnable.run();
}
@@ -843,15 +1060,15 @@ public class Launcher extends Activity
}
super.onResume();
- if (settingsChanged()) {
- android.os.Process.killProcess(android.os.Process.myPid());
+ if(isGelIntegrationEnabled() && isGelIntegrationSupported()) {
+ GelIntegrationHelper.getInstance().handleGelResume();
}
// Restore the previous launcher state
if (mOnResumeState == State.WORKSPACE) {
showWorkspace(false);
} else if (mOnResumeState == State.APPS_CUSTOMIZE) {
- showAllApps(false, AppsCustomizePagedView.ContentType.Applications, false);
+ showAllApps(false, mAppsCustomizeContent.getContentType(), false);
}
mOnResumeState = State.NONE;
@@ -862,7 +1079,7 @@ public class Launcher extends Activity
sPausedFromUserAction = false;
if (mRestoring || mOnResumeNeedsLoad) {
mWorkspaceLoading = true;
- mModel.startLoader(true, -1);
+ mModel.startLoader(true, PagedView.INVALID_RESTORE_PAGE);
mRestoring = false;
mOnResumeNeedsLoad = false;
}
@@ -937,6 +1154,29 @@ public class Launcher extends Activity
mWorkspace.updateInteractionForState();
mWorkspace.onResume();
mAppsCustomizeContent.onResume();
+
+ //Close out Fragments
+ Fragment f = getFragmentManager().findFragmentByTag(
+ TransitionEffectsFragment.TRANSITION_EFFECTS_FRAGMENT);
+ if (f != null) {
+ mTransitionEffectsFragment.setEffect();
+ }
+ f = getFragmentManager().findFragmentByTag(
+ DynamicGridSizeFragment.DYNAMIC_GRID_SIZE_FRAGMENT);
+ if (f != null) {
+ mDynamicGridSizeFragment.setSize();
+ mWorkspace.hideOutlines();
+ }
+ Fragment f1 = getFragmentManager().findFragmentByTag(
+ HiddenFolderFragment.HIDDEN_FOLDER_FRAGMENT);
+ if (f1 != null && !mHiddenFolderAuth) {
+ mHiddenFolderFragment.saveHiddenFolderStatus(-1);
+ FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
+ fragmentTransaction
+ .remove(mHiddenFolderFragment).commit();
+ } else {
+ mHiddenFolderAuth = false;
+ }
}
@Override
@@ -954,12 +1194,9 @@ public class Launcher extends Activity
if (mWorkspace.getCustomContentCallbacks() != null) {
mWorkspace.getCustomContentCallbacks().onHide();
}
- }
- protected void onFinishBindingItems() {
- if (mWorkspace != null && hasCustomContentToLeft() && mWorkspace.hasCustomContent()) {
- addCustomContentToLeft();
- }
+ //Reset the OverviewPanel position
+ ((SlidingUpPanelLayout) mOverviewPanel).collapsePane();
}
QSBScroller mQsbScroller = new QSBScroller() {
@@ -969,6 +1206,7 @@ public class Launcher extends Activity
public void setScrollY(int scroll) {
scrollY = scroll;
+ // Be careful of feature check isSearchBarEnabled
if (mWorkspace.isOnOrMovingToCustomContent()) {
mSearchDropTargetBar.setTranslationY(- scrollY);
getQsbBar().setTranslationY(-scrollY);
@@ -978,7 +1216,9 @@ public class Launcher extends Activity
public void resetQSBScroll() {
mSearchDropTargetBar.animate().translationY(0).start();
- getQsbBar().animate().translationY(0).start();
+ if (isSearchBarEnabled()) {
+ getQsbBar().animate().translationY(0).start();
+ }
}
public interface CustomContentCallbacks {
@@ -992,17 +1232,11 @@ public class Launcher extends Activity
public void onScrollProgressChanged(float progress);
}
- protected void startSettings() {
- Intent settings;
- // If we are on CyanogenMod the launcher settings are accessed from system settings.
- if (!getPackageManager().hasSystemFeature("com.cyanogenmod.android")) {
- settings = new Intent().setClass(this, SettingsActivity.class);
- settings.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
- | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
- } else {
- settings = new Intent(android.provider.Settings.ACTION_SETTINGS);
- }
+ protected void startThemeSettings() {
+ Intent settings = new Intent().setClassName(OverviewSettingsPanel.ANDROID_SETTINGS,
+ OverviewSettingsPanel.THEME_SETTINGS);
startActivity(settings);
+
if (mWorkspace.isInOverviewMode()) {
mWorkspace.exitOverviewMode(false);
} else if (mAppsCustomizeContent.isInOverviewMode()) {
@@ -1038,89 +1272,88 @@ public class Launcher extends Activity
mAppsCustomizeContent.setSortMode(AppsCustomizePagedView.SortMode.LaunchCount);
break;
}
+ mOverviewSettingsPanel.notifyDataSetInvalidated();
+ SettingsProvider.putInt(getBaseContext(), SettingsProvider.SETTINGS_UI_DRAWER_SORT_MODE,
+ mAppsCustomizeContent.getSortMode().getValue());
return true;
}
});
popupMenu.show();
}
- public void onClickTransitionEffectButton(View v) {
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ public void onClickDynamicGridSizeButton() {
+ FragmentManager fragmentManager = getFragmentManager();
+ FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
- // Load values
- final PagedView pagedView = isAllAppsVisible() ? mAppsCustomizeContent : mWorkspace;
- final PagedView.TransitionEffect oldEffect = pagedView.getTransitionEffect();
- final String oldEffectName = oldEffect != null ? oldEffect.getName() :
- PagedView.TransitionEffect.TRANSITION_EFFECT_NONE;
+ mDynamicGridSizeFragment = new DynamicGridSizeFragment();
+ fragmentTransaction.replace(R.id.launcher, mDynamicGridSizeFragment,
+ DynamicGridSizeFragment.DYNAMIC_GRID_SIZE_FRAGMENT);
+ fragmentTransaction.commit();
+ }
- final String[] titles = getResources().getStringArray(R.array.transition_effect_entries);
- final String[] values = getResources().getStringArray(R.array.transition_effect_values);
+ public void setDynamicGridSize(DeviceProfile.GridSize size) {
+ SettingsProvider.putInt(this,
+ SettingsProvider.SETTINGS_UI_DYNAMIC_GRID_SIZE, size.getValue());
- int selected = -1;
- for (int i = values.length - 1; i >= 0; i--) {
- if (values[i].equals(oldEffectName)) {
- selected = i;
- break;
- }
- }
+ updateDynamicGrid();
- // Create title view with overflow menu
- View customTitle = getLayoutInflater().inflate(R.layout.dialog_title_overflow_menu, null);
- TextView title = (TextView) customTitle.findViewById(android.R.id.title);
- title.setText(R.string.transition_effect_button_text);
+ mOverviewSettingsPanel.notifyDataSetInvalidated();
- View overflowMenu = customTitle.findViewById(R.id.overflow_menu_button);
- overflowMenu.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- onClickTransitionEffectOverflowMenuButton(v);
- }
- });
+ FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
+ fragmentTransaction
+ .setCustomAnimations(0, R.anim.exit_out_right);
+ fragmentTransaction
+ .remove(mDynamicGridSizeFragment).commit();
- builder.setCustomTitle(customTitle);
+ mDarkPanel.setVisibility(View.VISIBLE);
+ ObjectAnimator anim = ObjectAnimator.ofFloat(
+ mDarkPanel, "alpha", 0.3f, 0.0f);
+ anim.start();
+ anim.addListener(mAnimatorListener);
- builder.setSingleChoiceItems(titles, selected, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- String effect = values[which];
- PagedView.TransitionEffect.setFromString(pagedView, effect);
-
- // Show the changes immediately
- final int currentPage = pagedView.getNextPage();
- final int nextPage = currentPage + (currentPage != pagedView.getPageCount() - 1 ? 1 : -1);
- pagedView.snapToPageImmediately(currentPage);
- pagedView.snapToPage(nextPage, new Runnable() {
- @Override
- public void run() {
- pagedView.snapToPage(currentPage);
- }
- });
+ }
- SettingsProvider.get(Launcher.this).edit()
- .putString(!isAllAppsVisible() ?
- SettingsProvider.SETTINGS_UI_HOMESCREEN_SCROLLING_TRANSITION_EFFECT :
- SettingsProvider.SETTINGS_UI_DRAWER_SCROLLING_TRANSITION_EFFECT, effect)
- .commit();
- }
- });
+ public void onClickTransitionEffectButton(View v, final boolean pageOrDrawer) {
+ Bundle bundle = new Bundle();
+ bundle.putBoolean(TransitionEffectsFragment.PAGE_OR_DRAWER_SCROLL_SELECT,
+ pageOrDrawer);
+ FragmentManager fragmentManager = getFragmentManager();
+ FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
- if (isAllAppsVisible()) {
- mAppsCustomizeContent.exitOverviewMode(true);
- } else {
- mWorkspace.exitOverviewMode(true);
- }
+ mTransitionEffectsFragment = new TransitionEffectsFragment();
+ mTransitionEffectsFragment.setArguments(bundle);
+ fragmentTransaction.setCustomAnimations(0, 0);
+ fragmentTransaction.replace(R.id.launcher, mTransitionEffectsFragment,
+ TransitionEffectsFragment.TRANSITION_EFFECTS_FRAGMENT);
+ fragmentTransaction.commit();
+ }
- builder.setPositiveButton(android.R.string.ok, null)
- .setOnDismissListener(new DialogInterface.OnDismissListener() {
- @Override
- public void onDismiss(DialogInterface dialog) {
- mTransitionEffectDialog = null;
- }
- });
- mTransitionEffectDialog = builder.create();
- mTransitionEffectDialog.show();
- mTransitionEffectDialog.setCanceledOnTouchOutside(true);
- mTransitionEffectDialog.getWindow().getDecorView().setAlpha(0.6f);
+ public void setTransitionEffect(boolean pageOrDrawer, String newTransitionEffect) {
+ String mSettingsProviderValue = pageOrDrawer ?
+ SettingsProvider.SETTINGS_UI_DRAWER_SCROLLING_TRANSITION_EFFECT
+ : SettingsProvider.SETTINGS_UI_HOMESCREEN_SCROLLING_TRANSITION_EFFECT;
+ PagedView pagedView = pageOrDrawer ? mAppsCustomizeContent : mWorkspace;
+
+ SettingsProvider
+ .get(getApplicationContext())
+ .edit()
+ .putString(mSettingsProviderValue,
+ newTransitionEffect).commit();
+ TransitionEffect.setFromString(pagedView, newTransitionEffect);
+
+ mOverviewSettingsPanel.notifyDataSetInvalidated();
+
+ FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
+ fragmentTransaction
+ .setCustomAnimations(0, R.anim.exit_out_right);
+ fragmentTransaction
+ .remove(mTransitionEffectsFragment).commit();
+
+ mDarkPanel.setVisibility(View.VISIBLE);
+ ObjectAnimator anim = ObjectAnimator.ofFloat(
+ mDarkPanel, "alpha", 0.3f, 0.0f);
+ anim.start();
+ anim.addListener(mAnimatorListener);
}
public void onClickTransitionEffectOverflowMenuButton(View v) {
@@ -1175,6 +1408,13 @@ public class Launcher extends Activity
popupMenu.show();
}
+ protected boolean hasSettings() {
+ return false;
+ }
+
+ protected void startSettings() {
+ }
+
public interface QSBScroller {
public void setScrollY(int scrollY);
}
@@ -1271,6 +1511,7 @@ public class Launcher extends Activity
*
* @param savedState The previous state.
*/
+ @SuppressWarnings("unchecked")
private void restoreState(Bundle savedState) {
if (savedState == null) {
return;
@@ -1298,6 +1539,7 @@ public class Launcher extends Activity
mPendingAddInfo.spanX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_X);
mPendingAddInfo.spanY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y);
mPendingAddWidgetInfo = savedState.getParcelable(RUNTIME_STATE_PENDING_ADD_WIDGET_INFO);
+ mPendingAddWidgetId = savedState.getInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID);
mWaitingForResult = true;
mRestoring = true;
}
@@ -1321,6 +1563,8 @@ public class Launcher extends Activity
int currentIndex = savedState.getInt("apps_customize_currentIndex");
mAppsCustomizeContent.restorePageForIndex(currentIndex);
}
+ mItemIdToViewId = (HashMap<Integer, Integer>)
+ savedState.getSerializable(RUNTIME_STATE_VIEW_IDS);
}
/**
@@ -1332,6 +1576,7 @@ public class Launcher extends Activity
mLauncherView = findViewById(R.id.launcher);
mDragLayer = (DragLayer) findViewById(R.id.drag_layer);
mWorkspace = (Workspace) mDragLayer.findViewById(R.id.workspace);
+ mPageIndicators = mDragLayer.findViewById(R.id.page_indicator);
mLauncherView.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
@@ -1348,78 +1593,11 @@ public class Launcher extends Activity
}
mOverviewPanel = findViewById(R.id.overview_panel);
- mOverviewPanel.setAlpha(0f);
- View widgetButton = findViewById(R.id.widget_button);
- widgetButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View arg0) {
- showAllApps(true, AppsCustomizePagedView.ContentType.Widgets, true);
- }
- });
- widgetButton.setOnTouchListener(getHapticFeedbackTouchListener());
-
- View wallpaperButton = findViewById(R.id.wallpaper_button);
- wallpaperButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View arg0) {
- startWallpaper();
- }
- });
- wallpaperButton.setOnTouchListener(getHapticFeedbackTouchListener());
-
- View settingsButton = findViewById(R.id.settings_button);
- settingsButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View arg0) {
- startSettings();
- }
- });
- settingsButton.setOnTouchListener(getHapticFeedbackTouchListener());
-
- View defaultScreenButton = findViewById(R.id.default_screen_button);
- defaultScreenButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View arg0) {
- mWorkspace.onClickDefaultScreenButton();
- }
- });
- defaultScreenButton.setOnTouchListener(getHapticFeedbackTouchListener());
-
- View transitionEffectButton = findViewById(R.id.transition_effect_button);
- transitionEffectButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View arg0) {
- onClickTransitionEffectButton(arg0);
- }
- });
- transitionEffectButton.setOnTouchListener(getHapticFeedbackTouchListener());
-
- View iconPackButton = findViewById(R.id.icon_pack_button);
- iconPackButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View arg0) {
- IconPackHelper.pickIconPack(Launcher.this, false);
- }
- });
- iconPackButton.setOnTouchListener(getHapticFeedbackTouchListener());
-
- View sortButton = findViewById(R.id.sort_button);
- sortButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View arg0) {
- onClickSortModeButton(arg0);
- }
- });
- sortButton.setOnTouchListener(getHapticFeedbackTouchListener());
-
- View filterButton = findViewById(R.id.filter_button);
- filterButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
-
- }
- });
- filterButton.setOnTouchListener(getHapticFeedbackTouchListener());
+ mOverviewSettingsPanel = new OverviewSettingsPanel(
+ this, mOverviewPanel);
+ mOverviewSettingsPanel.initializeAdapter();
+ mOverviewSettingsPanel.initializeViews();
+ mDarkPanel = ((SlidingUpPanelLayout) mOverviewPanel).findViewById(R.id.dark_panel);
// Setup the workspace
mWorkspace.setHapticFeedbackEnabled(false);
@@ -1428,7 +1606,8 @@ public class Launcher extends Activity
dragController.addDragListener(mWorkspace);
// Get the search/delete bar
- mSearchDropTargetBar = (SearchDropTargetBar) mDragLayer.findViewById(R.id.qsb_bar);
+ mSearchDropTargetBar = (SearchDropTargetBar)
+ mDragLayer.findViewById(R.id.search_drop_target_bar);
// Setup AppsCustomize
mAppsCustomizeLayout = (AppsCustomizeLayout) findViewById(R.id.apps_customize_pane);
@@ -1494,7 +1673,6 @@ public class Launcher extends Activity
favorite.setCompoundDrawables(null, d, null, null);
favorite.setOnTouchListener(getHapticFeedbackTouchListener());
}
- Utilities.applyTypeface(favorite);
return favorite;
}
@@ -1662,11 +1840,12 @@ public class Launcher extends Activity
if (appWidgetId != -1) {
// Deleting an app widget ID is a void call but writes to disk before returning
// to the caller...
- new Thread("deleteAppWidgetId") {
- public void run() {
+ new AsyncTask<Void, Void, Void>() {
+ public Void doInBackground(Void ... args) {
mAppWidgetHost.deleteAppWidgetId(appWidgetId);
+ return null;
}
- }.start();
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
}
showOutOfSpaceMessage(isHotseatLayout(layout));
return;
@@ -1722,6 +1901,15 @@ public class Launcher extends Activity
} else if (Intent.ACTION_USER_PRESENT.equals(action)) {
mUserPresent = true;
updateRunning();
+ } else if (ENABLE_DEBUG_INTENTS && DebugIntents.DELETE_DATABASE.equals(action)) {
+ mModel.resetLoadedState(false, true);
+ mModel.startLoader(false, PagedView.INVALID_RESTORE_PAGE,
+ LauncherModel.LOADER_FLAG_CLEAR_WORKSPACE);
+ } else if (ENABLE_DEBUG_INTENTS && DebugIntents.MIGRATE_DATABASE.equals(action)) {
+ mModel.resetLoadedState(false, true);
+ mModel.startLoader(false, PagedView.INVALID_RESTORE_PAGE,
+ LauncherModel.LOADER_FLAG_CLEAR_WORKSPACE
+ | LauncherModel.LOADER_FLAG_MIGRATE_SHORTCUTS);
}
}
};
@@ -1734,6 +1922,10 @@ public class Launcher extends Activity
final IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_USER_PRESENT);
+ if (ENABLE_DEBUG_INTENTS) {
+ filter.addAction(DebugIntents.DELETE_DATABASE);
+ filter.addAction(DebugIntents.MIGRATE_DATABASE);
+ }
registerReceiver(mReceiver, filter);
FirstFrameAnimatorHelper.initializeDrawListener(getWindow().getDecorView());
mAttached = true;
@@ -1869,6 +2061,30 @@ public class Launcher extends Activity
Toast.makeText(this, getString(strId), Toast.LENGTH_SHORT).show();
}
+ public DragLayer getDragLayer() {
+ return mDragLayer;
+ }
+
+ public Workspace getWorkspace() {
+ return mWorkspace;
+ }
+
+ public Hotseat getHotseat() {
+ return mHotseat;
+ }
+
+ public View getDarkPanel() {
+ return mDarkPanel;
+ }
+
+ public View getOverviewPanel() {
+ return mOverviewPanel;
+ }
+
+ public SearchDropTargetBar getSearchBar() {
+ return mSearchDropTargetBar;
+ }
+
public LauncherAppWidgetHost getAppWidgetHost() {
return mAppWidgetHost;
}
@@ -1877,6 +2093,14 @@ public class Launcher extends Activity
return mModel;
}
+ public LauncherClings getLauncherClings() {
+ return mLauncherClings;
+ }
+
+ protected SharedPreferences getSharedPrefs() {
+ return mSharedPrefs;
+ }
+
public void closeSystemDialogs() {
getWindow().closeAllPanels();
@@ -1913,7 +2137,7 @@ public class Launcher extends Activity
// In all these cases, only animate if we're already on home
mWorkspace.exitWidgetResizeMode();
if (alreadyOnHome && mState == State.WORKSPACE && !mWorkspace.isTouchActive() &&
- openFolder == null) {
+ openFolder == null && shouldMoveToDefaultScreenOnHomeIntent()) {
mWorkspace.moveToDefaultScreen(true);
}
@@ -1936,9 +2160,11 @@ public class Launcher extends Activity
}
// Reset the apps customize page
- if (mAppsCustomizeLayout != null) {
+ if (!alreadyOnHome && mAppsCustomizeLayout != null) {
mAppsCustomizeLayout.reset();
}
+
+ onHomeIntent();
}
if (DEBUG_RESUME_TIME) {
@@ -1946,6 +2172,21 @@ public class Launcher extends Activity
}
}
+ /**
+ * Override point for subclasses to prevent movement to the default screen when the home
+ * button is pressed. Used (for example) in GEL, to prevent movement during a search.
+ */
+ protected boolean shouldMoveToDefaultScreenOnHomeIntent() {
+ return true;
+ }
+
+ /**
+ * Override point for subclasses to provide custom behaviour for when a home intent is fired.
+ */
+ protected void onHomeIntent() {
+ // Do nothing
+ }
+
@Override
public void onRestoreInstanceState(Bundle state) {
super.onRestoreInstanceState(state);
@@ -1957,7 +2198,8 @@ public class Launcher extends Activity
@Override
protected void onSaveInstanceState(Bundle outState) {
if (mWorkspace.getChildCount() > 0) {
- outState.putInt(RUNTIME_STATE_CURRENT_SCREEN, mWorkspace.getRestorePage());
+ outState.putInt(RUNTIME_STATE_CURRENT_SCREEN,
+ mWorkspace.getCurrentPageOffsetFromCustomContent());
}
super.onSaveInstanceState(outState);
@@ -1975,6 +2217,7 @@ public class Launcher extends Activity
outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_X, mPendingAddInfo.spanX);
outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y, mPendingAddInfo.spanY);
outState.putParcelable(RUNTIME_STATE_PENDING_ADD_WIDGET_INFO, mPendingAddWidgetInfo);
+ outState.putInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID, mPendingAddWidgetId);
}
if (mFolderInfo != null && mWaitingForResult) {
@@ -1984,6 +2227,7 @@ public class Launcher extends Activity
// Save the current AppsCustomize tab
if (mAppsCustomizeLayout != null) {
+ AppsCustomizePagedView.ContentType type = mAppsCustomizeContent.getContentType();
String currentTabTag = mAppsCustomizeContent.getContentType().name();
if (currentTabTag != null) {
outState.putString("apps_customize_currentContentType", currentTabTag);
@@ -1991,6 +2235,7 @@ public class Launcher extends Activity
int currentIndex = mAppsCustomizeContent.getSaveInstanceStateIndex();
outState.putInt("apps_customize_currentIndex", currentIndex);
}
+ outState.putSerializable(RUNTIME_STATE_VIEW_IDS, mItemIdToViewId);
}
@Override
@@ -2029,17 +2274,33 @@ public class Launcher extends Activity
mDragLayer.clearAllResizeFrames();
((ViewGroup) mWorkspace.getParent()).removeAllViews();
- mWorkspace.removeAllViews();
+ mWorkspace.removeAllWorkspaceScreens();
mWorkspace = null;
mDragController = null;
LauncherAnimUtils.onDestroyActivity();
+
+ unregisterReceiver(protectedAppsChangedReceiver);
}
public DragController getDragController() {
return mDragController;
}
+ public void validateLockForHiddenFolders(Bundle bundle, FolderIcon info) {
+ // Validate Lock Pattern
+ Intent lockPatternActivity = new Intent();
+ lockPatternActivity.setClassName(
+ "com.android.settings",
+ "com.android.settings.applications.LockPatternActivity");
+ startActivityForResult(lockPatternActivity, REQUEST_LOCK_PATTERN);
+ mHiddenFolderAuth = false;
+
+ mHiddenFolderIcon = info;
+ mHiddenFolderFragment = new HiddenFolderFragment();
+ mHiddenFolderFragment.setArguments(bundle);
+ }
+
@Override
public void startActivityForResult(Intent intent, int requestCode) {
if (requestCode >= 0) mWaitingForResult = true;
@@ -2119,29 +2380,28 @@ public class Launcher extends Activity
}
}
+ public boolean isOnCustomContent() {
+ return mWorkspace.isOnOrMovingToCustomContent();
+ }
+
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
- if (!isAllAppsVisible()) {
+ if (!isOnCustomContent()) {
+ // Close any open folders
+ closeFolder();
+ // Stop resizing any widgets
+ mWorkspace.exitWidgetResizeMode();
if (!mWorkspace.isInOverviewMode()) {
- mWorkspace.enterOverviewMode();
- } else {
- mWorkspace.exitOverviewMode(true);
- }
- } else {
- if (!mAppsCustomizeContent.isInOverviewMode()) {
- mAppsCustomizeContent.enterOverviewMode();
+ // Show the overview mode
+ showOverviewMode(true);
} else {
- mAppsCustomizeContent.exitOverviewMode(true);
+ showWorkspace(true);
}
}
return false;
}
- void enterAllAppsOverviewMode() {
- mAppsCustomizeContent.enterOverviewMode();
- }
-
@Override
public boolean onSearchRequested() {
startSearch(null, false, null, true);
@@ -2153,6 +2413,10 @@ public class Launcher extends Activity
return mWorkspaceLoading || mWaitingForResult;
}
+ public boolean isWorkspaceLoading() {
+ return mWorkspaceLoading;
+ }
+
private void resetAddInfo() {
mPendingAddInfo.container = ItemInfo.NO_ID;
mPendingAddInfo.screenId = -1;
@@ -2162,10 +2426,17 @@ public class Launcher extends Activity
mPendingAddInfo.dropPos = null;
}
- void addAppWidgetImpl(final int appWidgetId, ItemInfo info, AppWidgetHostView boundWidget,
- AppWidgetProviderInfo appWidgetInfo) {
+ void addAppWidgetImpl(final int appWidgetId, final ItemInfo info,
+ final AppWidgetHostView boundWidget, final AppWidgetProviderInfo appWidgetInfo) {
+ addAppWidgetImpl(appWidgetId, info, boundWidget, appWidgetInfo, 0);
+ }
+
+ void addAppWidgetImpl(final int appWidgetId, final ItemInfo info,
+ final AppWidgetHostView boundWidget, final AppWidgetProviderInfo appWidgetInfo, int
+ delay) {
if (appWidgetInfo.configure != null) {
mPendingAddWidgetInfo = appWidgetInfo;
+ mPendingAddWidgetId = appWidgetId;
// Launch over to configure widget, if needed
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE);
@@ -2174,10 +2445,17 @@ public class Launcher extends Activity
Utilities.startActivityForResultSafely(this, intent, REQUEST_CREATE_APPWIDGET);
} else {
// Otherwise just add it
+ Runnable onComplete = new Runnable() {
+ @Override
+ public void run() {
+ // Exit spring loaded mode if necessary after adding the widget
+ exitSpringLoadedDragModeDelayed(true, EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT,
+ null);
+ }
+ };
completeAddAppWidget(appWidgetId, info.container, info.screenId, boundWidget,
appWidgetInfo);
- // Exit spring loaded mode if necessary after adding the widget
- exitSpringLoadedDragModeDelayed(true, false, null);
+ mWorkspace.removeExtraEmptyScreen(true, onComplete, delay, false);
}
}
@@ -2327,7 +2605,7 @@ public class Launcher extends Activity
}
protected ComponentName getWallpaperPickerComponent() {
- return new ComponentName(WALLPAPER_PICKER_PACKAGE, WALLPAPER_PICKER_ACTIVITY);
+ return new ComponentName(getPackageName(), LauncherWallpaperPickerActivity.class.getName());
}
/**
@@ -2365,10 +2643,15 @@ public class Launcher extends Activity
@Override
public void onBackPressed() {
+ Fragment f1 = getFragmentManager().findFragmentByTag(
+ HiddenFolderFragment.HIDDEN_FOLDER_FRAGMENT);
+ if (f1 != null) {
+ mHiddenFolderFragment.saveHiddenFolderStatus(-1);
+ FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
+ fragmentTransaction
+ .remove(mHiddenFolderFragment).commit();
+ }
if (isAllAppsVisible()) {
- if (isClingsEnabled()) {
- dismissAllAppsCling(null);
- }
if (mAppsCustomizeContent.isInOverviewMode()) {
mAppsCustomizeContent.exitOverviewMode(true);
} else {
@@ -2380,7 +2663,17 @@ public class Launcher extends Activity
}
}
} else if (mWorkspace.isInOverviewMode()) {
- mWorkspace.exitOverviewMode(true);
+ Fragment f = getFragmentManager().findFragmentByTag(
+ TransitionEffectsFragment.TRANSITION_EFFECTS_FRAGMENT);
+ Fragment f2 = getFragmentManager().findFragmentByTag(
+ DynamicGridSizeFragment.DYNAMIC_GRID_SIZE_FRAGMENT);
+ if (f != null) {
+ mTransitionEffectsFragment.setEffect();
+ } else if (f2 != null) {
+ mDynamicGridSizeFragment.setSize();
+ } else {
+ mWorkspace.exitOverviewMode(true);
+ }
} else if (mWorkspace.getOpenFolder() != null) {
Folder openFolder = mWorkspace.getOpenFolder();
if (openFolder.isEditingName()) {
@@ -2454,7 +2747,7 @@ public class Launcher extends Activity
final String shortcutClass = intent.getComponent().getClassName();
if (shortcutClass.equals(WidgetAdder.class.getName())) {
- showAllApps(true, AppsCustomizePagedView.ContentType.Widgets, true);
+ onClickAddWidgetButton();
return;
} else if (shortcutClass.equals(MemoryDumpActivity.class.getName())) {
MemoryDumpActivity.startDump(this);
@@ -2504,8 +2797,12 @@ public class Launcher extends Activity
* @param v The view that was clicked.
*/
public void onClickSearchButton(View v) {
- v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
+ if (hasCustomSearchSupport()) {
+ requestSearch(Home.MODE_SEARCH_TEXT);
+ return;
+ }
+ v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
onSearchRequested();
}
@@ -2515,8 +2812,12 @@ public class Launcher extends Activity
* @param v The view that was clicked.
*/
public void onClickVoiceButton(View v) {
- v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
+ if (hasCustomSearchSupport()) {
+ requestSearch(Home.MODE_SEARCH_VOICE);
+ return;
+ }
+ v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
startVoice();
}
@@ -2545,7 +2846,15 @@ public class Launcher extends Activity
* @param v The view that was clicked.
*/
public void onClickAllAppsButton(View v) {
- showAllApps(true, AppsCustomizePagedView.ContentType.Applications, true);
+ showAllApps(true, AppsCustomizePagedView.ContentType.Applications, false);
+ }
+
+ /**
+ * Event handler for the (Add) Widgets button that appears after a long press
+ * on the home screen.
+ */
+ protected void onClickAddWidgetButton() {
+ showAllApps(true, AppsCustomizePagedView.ContentType.Widgets, true);
}
public void onTouchDownAllAppsButton(View v) {
@@ -2592,9 +2901,10 @@ public class Launcher extends Activity
void startApplicationDetailsActivity(ComponentName componentName) {
String packageName = componentName.getPackageName();
- Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
+ Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Uri.fromParts("package", packageName, null));
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK |
+ Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
startActivitySafely(null, intent, "startApplicationDetailsActivity");
}
@@ -2804,6 +3114,11 @@ public class Launcher extends Activity
Folder folder = folderIcon.getFolder();
FolderInfo info = folder.mInfo;
+ if (info.hidden) {
+ folder.startHiddenFolderManager();
+ return;
+ }
+
info.opened = true;
// Just verify that the folder hasn't already been added to the DragLayer.
@@ -2825,7 +3140,7 @@ public class Launcher extends Activity
}
public void closeFolder() {
- Folder folder = mWorkspace.getOpenFolder();
+ Folder folder = mWorkspace != null ? mWorkspace.getOpenFolder() : null;
if (folder != null) {
if (folder.isEditingName()) {
folder.dismissEditingName();
@@ -2833,7 +3148,7 @@ public class Launcher extends Activity
closeFolder(folder);
// Dismiss the folder cling
- dismissFolderCling(null);
+ mLauncherClings.dismissFolderCling(null);
}
}
@@ -2883,7 +3198,8 @@ public class Launcher extends Activity
// The hotseat touch handling does not go through Workspace, and we always allow long press
// on hotseat items.
final View itemUnderLongClick = longClickCellInfo.cell;
- boolean allowLongPress = isHotseatLayout(v) || mWorkspace.allowLongPress();
+ final boolean inHotseat = isHotseatLayout(v);
+ boolean allowLongPress = inHotseat || mWorkspace.allowLongPress();
if (allowLongPress && !mDragController.isDragging()) {
if (itemUnderLongClick == null) {
// User long pressed on empty space
@@ -2909,15 +3225,6 @@ public class Launcher extends Activity
return mHotseat != null && layout != null &&
(layout instanceof CellLayout) && (layout == mHotseat.getLayout());
}
- Hotseat getHotseat() {
- return mHotseat;
- }
- View getOverviewPanel() {
- return mOverviewPanel;
- }
- SearchDropTargetBar getSearchBar() {
- return mSearchDropTargetBar;
- }
/**
* Returns the CellLayout of the specified container at the specified screen.
@@ -2934,39 +3241,12 @@ public class Launcher extends Activity
}
}
- protected Workspace getWorkspace() {
- return mWorkspace;
+ public AppsCustomizePagedView getAppsCustomizeContent() {
+ return mAppsCustomizeContent;
}
public void updateOverviewPanel() {
- View defaultScreenButton = mOverviewPanel.findViewById(R.id.default_screen_button);
- View transitionEffectButton = mOverviewPanel.findViewById(R.id.transition_effect_button);
- View widgetButton = mOverviewPanel.findViewById(R.id.widget_button);
- View wallpaperButton = mOverviewPanel.findViewById(R.id.wallpaper_button);
- View sortButton = mOverviewPanel.findViewById(R.id.sort_button);
- View filterButton = mOverviewPanel.findViewById(R.id.filter_button);
- View iconPackButton = findViewById(R.id.icon_pack_button);
-
- PagedView pagedView = !isAllAppsVisible() ? mWorkspace : mAppsCustomizeContent;
-
- defaultScreenButton.setVisibility((!isAllAppsVisible() && pagedView.getPageCount() > 1) ? View.VISIBLE : View.GONE);
- transitionEffectButton.setVisibility(pagedView.getPageCount() > 1 ? View.VISIBLE : View.GONE);
- widgetButton.setVisibility(!isAllAppsVisible() ? View.VISIBLE : View.GONE);
- wallpaperButton.setVisibility(!isAllAppsVisible() ? View.VISIBLE : View.GONE);
- sortButton.setVisibility(isAllAppsVisible() ? View.VISIBLE : View.GONE);
- // TODO: implement filtering
- // filterButton.setVisibility(isAllAppsVisible() ? View.VISIBLE : View.GONE);
- filterButton.setVisibility(View.GONE);
-
- boolean isVisible = !isAllAppsVisible();
- if (isVisible) {
- int numIconPacks = IconPackHelper.getSupportedPackages(this).size();
- isVisible = numIconPacks > 0;
- }
- iconPackButton.setVisibility(isVisible ? View.VISIBLE : View.GONE);
-
- // Make sure overview panel is drawn above apps customize
- mOverviewPanel.bringToFront();
+ mOverviewSettingsPanel.update();
}
public boolean isAllAppsVisible() {
@@ -3077,23 +3357,6 @@ public class Launcher extends Activity
AppsCustomizePagedView.ContentType contentType = mAppsCustomizeContent.getContentType();
showAppsCustomizeHelper(animated, springLoaded, contentType);
}
-
- public void showAllAppsCling() {
- if (isClingsEnabled() &&
- !mSharedPrefs.getBoolean(Cling.ALL_APPS_CLING_DISMISSED_KEY, false) &&
- !skipCustomClingIfNoAccounts() ) {
- Cling cling = (Cling) findViewById(R.id.all_apps_cling);
- View pageIndicator = mAppsCustomizeLayout.findViewById(R.id.page_indicator);
- cling.setPunchThroughForView(pageIndicator);
- if (cling != null) {
- cling.bringToFront();
- }
- initCling(R.id.all_apps_cling, 0, true, true);
- } else {
- removeCling(R.id.all_apps_cling);
- }
- }
-
private void showAppsCustomizeHelper(final boolean animated, final boolean springLoaded,
final AppsCustomizePagedView.ContentType contentType) {
if (mStateAnimation != null) {
@@ -3116,8 +3379,9 @@ public class Launcher extends Activity
// Shrink workspaces away if going to AppsCustomize from workspace
Animator workspaceAnim =
mWorkspace.getChangeStateAnimation(Workspace.State.SMALL, animated);
- if (!AppsCustomizePagedView.DISABLE_ALL_APPS) {
- // Set the content type for the all apps space
+ if (!LauncherAppState.isDisableAllApps()
+ || contentType == AppsCustomizePagedView.ContentType.Widgets) {
+ // Set the content type for the all apps/widgets space
mAppsCustomizeContent.setContentType(contentType);
}
@@ -3173,10 +3437,6 @@ public class Launcher extends Activity
if (mSearchDropTargetBar != null) {
mSearchDropTargetBar.hideSearchBar(false);
}
-
- if (contentType == AppsCustomizePagedView.ContentType.Applications) {
- showAllAppsCling();
- }
}
});
@@ -3304,7 +3564,7 @@ public class Launcher extends Activity
dispatchOnLauncherTransitionPrepare(fromView, animated, true);
dispatchOnLauncherTransitionPrepare(toView, animated, true);
- mAppsCustomizeContent.pauseScrolling();
+ mAppsCustomizeContent.stopScrolling();
mStateAnimation.addListener(new AnimatorListenerAdapter() {
@Override
@@ -3316,7 +3576,6 @@ public class Launcher extends Activity
onCompleteRunnable.run();
}
mAppsCustomizeContent.updateCurrentPageScroll();
- mAppsCustomizeContent.resumeScrolling();
}
});
@@ -3433,7 +3692,7 @@ public class Launcher extends Activity
}
}
- void exitSpringLoadedDragModeDelayed(final boolean successfulDrop, boolean extendedDelay,
+ void exitSpringLoadedDragModeDelayed(final boolean successfulDrop, int delay,
final Runnable onCompleteRunnable) {
if (mState != State.APPS_CUSTOMIZE_SPRING_LOADED) return;
@@ -3450,9 +3709,8 @@ public class Launcher extends Activity
exitSpringLoadedDragMode();
}
}
- }, (extendedDelay ?
- EXIT_SPRINGLOADED_MODE_LONG_TIMEOUT :
- EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT));
+ }, delay);
+
}
void exitSpringLoadedDragMode() {
@@ -3626,11 +3884,25 @@ public class Launcher extends Activity
}
public View getQsbBar() {
- if (mQsbBar == null) {
- mQsbBar = mInflater.inflate(R.layout.search_bar, mSearchDropTargetBar, false);
- mSearchDropTargetBar.addView(mQsbBar);
+ if (mQsb == null) {
+ mQsb = mInflater.inflate(R.layout.qsb, mSearchDropTargetBar, false);
+ mSearchDropTargetBar.addView(mQsb);
+ }
+ return mQsb;
+ }
+
+ public ImageView getQsbBarSearchButton() {
+ if (mQsbBarSearch == null && getQsbBar() != null) {
+ return (ImageView) getQsbBar().findViewById(R.id.search_button);
}
- return mQsbBar;
+ return mQsbBarSearch;
+ }
+
+ public ImageView getQsbBarVoiceButton() {
+ if (mQsbBarVoice == null && getQsbBar() != null) {
+ return (ImageView) getQsbBar().findViewById(R.id.voice_button);
+ }
+ return mQsbBarVoice;
}
protected boolean updateGlobalSearchIcon() {
@@ -3887,14 +4159,18 @@ public class Launcher extends Activity
// Create the custom content page (this call updates mDefaultScreen which calls
// setCurrentPage() so ensure that all pages are added before calling this).
- // The actual content of the custom page will be added during onFinishBindingItems().
- if (!mWorkspace.hasCustomContent() && hasCustomContentToLeft()) {
- mWorkspace.createCustomContentPage();
+ if (hasCustomContentToLeft()) {
+ mWorkspace.createCustomContentContainer();
+ populateCustomContentContainer();
}
}
@Override
public void bindAddScreens(ArrayList<Long> orderedScreenIds) {
+ // Log to disk
+ Launcher.addDumpLog(TAG, "11683562 - bindAddScreens()", true);
+ Launcher.addDumpLog(TAG, "11683562 - orderedScreenIds: " +
+ TextUtils.join(", ", orderedScreenIds), true);
int count = orderedScreenIds.size();
for (int i = 0; i < count; i++) {
mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(orderedScreenIds.get(i));
@@ -3939,23 +4215,25 @@ public class Launcher extends Activity
}
// Add the new screens
- bindAddScreens(newScreens);
+ if (newScreens != null) {
+ bindAddScreens(newScreens);
+ }
// We add the items without animation on non-visible pages, and with
// animations on the new page (which we will try and snap to).
- if (!addNotAnimated.isEmpty()) {
+ if (addNotAnimated != null && !addNotAnimated.isEmpty()) {
bindItems(addNotAnimated, 0,
addNotAnimated.size(), false);
}
- if (!addAnimated.isEmpty()) {
+ if (addAnimated != null && !addAnimated.isEmpty()) {
bindItems(addAnimated, 0,
addAnimated.size(), true);
}
// Remove the extra empty screen
- mWorkspace.removeExtraEmptyScreens();
+ mWorkspace.removeExtraEmptyScreen(false, null);
- if (!AppsCustomizePagedView.DISABLE_ALL_APPS &&
+ if (!LauncherAppState.isDisableAllApps() &&
addedApps != null && mAppsCustomizeContent != null) {
mAppsCustomizeContent.addApps(addedApps);
}
@@ -4053,9 +4331,11 @@ public class Launcher extends Activity
// when we are loading right after we return to launcher.
mWorkspace.postDelayed(new Runnable() {
public void run() {
- mWorkspace.snapToPage(newScreenIndex);
- mWorkspace.postDelayed(startBounceAnimRunnable,
- NEW_APPS_ANIMATION_DELAY);
+ if (mWorkspace != null) {
+ mWorkspace.snapToPage(newScreenIndex);
+ mWorkspace.postDelayed(startBounceAnimRunnable,
+ NEW_APPS_ANIMATION_DELAY);
+ }
}
}, NEW_APPS_PAGE_MOVE_DELAY);
} else {
@@ -4165,13 +4445,6 @@ public class Launcher extends Activity
mWorkspace.getUniqueComponents(true, null);
mIntentsOnWorkspaceFromUpgradePath = mWorkspace.getUniqueComponents(true, null);
}
-
- mWorkspace.post(new Runnable() {
- @Override
- public void run() {
- onFinishBindingItems();
- }
- });
}
private boolean canRunNewAppsAnimation() {
@@ -4190,6 +4463,16 @@ public class Launcher extends Activity
return bounceAnim;
}
+ public boolean useVerticalBarLayout() {
+ return LauncherAppState.getInstance().getDynamicGrid().
+ getDeviceProfile().isVerticalBarLayout();
+ }
+
+ protected Rect getSearchBarBounds() {
+ return LauncherAppState.getInstance().getDynamicGrid().
+ getDeviceProfile().getSearchBarBounds();
+ }
+
@Override
public void bindSearchablesChanged() {
boolean searchVisible = updateGlobalSearchIcon();
@@ -4205,7 +4488,7 @@ public class Launcher extends Activity
* Implementation of the method from LauncherModel.Callbacks.
*/
public void bindAllApplications(final ArrayList<AppInfo> apps) {
- if (AppsCustomizePagedView.DISABLE_ALL_APPS) {
+ if (LauncherAppState.isDisableAllApps()) {
if (mIntentsOnWorkspaceFromUpgradePath != null) {
if (LauncherModel.UPGRADE_USE_MORE_APPS_FOLDER) {
getHotseat().addAllAppsFolder(mIconCache, apps,
@@ -4213,9 +4496,15 @@ public class Launcher extends Activity
}
mIntentsOnWorkspaceFromUpgradePath = null;
}
+ if (mAppsCustomizeContent != null) {
+ mAppsCustomizeContent.onPackagesUpdated(
+ LauncherModel.getSortedWidgetsAndShortcuts(this));
+ }
} else {
if (mAppsCustomizeContent != null) {
mAppsCustomizeContent.setApps(apps);
+ mAppsCustomizeContent.onPackagesUpdated(
+ LauncherModel.getSortedWidgetsAndShortcuts(this));
}
}
}
@@ -4239,7 +4528,7 @@ public class Launcher extends Activity
mWorkspace.updateShortcuts(apps);
}
- if (!AppsCustomizePagedView.DISABLE_ALL_APPS &&
+ if (!LauncherAppState.isDisableAllApps() &&
mAppsCustomizeContent != null) {
mAppsCustomizeContent.updateApps(apps);
}
@@ -4255,27 +4544,28 @@ public class Launcher extends Activity
* Implementation of the method from LauncherModel.Callbacks.
*/
public void bindComponentsRemoved(final ArrayList<String> packageNames,
- final ArrayList<AppInfo> appInfos,
- final boolean packageRemoved) {
+ final ArrayList<AppInfo> appInfos) {
Runnable r = new Runnable() {
public void run() {
- bindComponentsRemoved(packageNames, appInfos, packageRemoved);
+ bindComponentsRemoved(packageNames, appInfos);
}
};
if (waitUntilResume(r)) {
return;
}
- if (packageRemoved) {
+ if (!packageNames.isEmpty()) {
mWorkspace.removeItemsByPackageName(packageNames);
- } else {
+ }
+ if (!appInfos.isEmpty()) {
mWorkspace.removeItemsByApplicationInfo(appInfos);
}
// Notify the drag controller
- mDragController.onAppsRemoved(appInfos, this);
+ mDragController.onAppsRemoved(packageNames, appInfos);
- if (!AppsCustomizePagedView.DISABLE_ALL_APPS &&
+ // Update AllApps
+ if (!LauncherAppState.isDisableAllApps() &&
mAppsCustomizeContent != null) {
mAppsCustomizeContent.removeApps(appInfos);
}
@@ -4291,7 +4581,6 @@ public class Launcher extends Activity
mWidgetsAndShortcuts = null;
}
};
-
public void bindPackagesUpdated(final ArrayList<Object> widgetsAndShortcuts) {
if (waitUntilResume(mBindPackagesUpdatedRunnable, true)) {
mWidgetsAndShortcuts = widgetsAndShortcuts;
@@ -4299,8 +4588,7 @@ public class Launcher extends Activity
}
// Update the widgets pane
- if (!AppsCustomizePagedView.DISABLE_ALL_APPS &&
- mAppsCustomizeContent != null) {
+ if (mAppsCustomizeContent != null) {
mAppsCustomizeContent.onPackagesUpdated(widgetsAndShortcuts);
}
}
@@ -4362,202 +4650,27 @@ public class Launcher extends Activity
}
}
- /* Cling related */
- private boolean isClingsEnabled() {
- if (DISABLE_CLINGS) {
- return false;
- }
-
- // disable clings when running in a test harness
- if(ActivityManager.isRunningInTestHarness()) return false;
-
- // Disable clings for accessibility when explore by touch is enabled
- final AccessibilityManager a11yManager = (AccessibilityManager) getSystemService(
- ACCESSIBILITY_SERVICE);
- if (a11yManager.isTouchExplorationEnabled()) {
- return false;
- }
-
- // 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(this).getAccounts();
-// if (supportsLimitedUsers && accounts.length == 0) {
-// UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
-// Bundle restrictions = um.getUserRestrictions();
-// if (restrictions.getBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS, false)) {
-// return false;
-// }
-// }
- return true;
- }
-
- private Cling initCling(int clingId, int scrimId, boolean animate,
- boolean dimNavBarVisibilty) {
- Cling cling = (Cling) findViewById(clingId);
- View scrim = null;
- if (scrimId > 0) {
- scrim = findViewById(R.id.cling_scrim);
- }
- if (cling != null) {
- cling.init(this, scrim);
- cling.show(animate, SHOW_CLING_DURATION);
-
- if (dimNavBarVisibilty) {
- cling.setSystemUiVisibility(cling.getSystemUiVisibility() |
- View.SYSTEM_UI_FLAG_LOW_PROFILE);
- }
- }
- return cling;
- }
-
- private void dismissCling(final Cling cling, final Runnable postAnimationCb,
- final String flag, int duration, boolean restoreNavBarVisibilty) {
- // To catch cases where siblings of top-level views are made invisible, just check whether
- // the cling is directly set to GONE before dismissing it.
- if (cling != null && cling.getVisibility() != View.GONE) {
- final Runnable cleanUpClingCb = new Runnable() {
- public void run() {
- cling.cleanup();
- // We should update the shared preferences on a background thread
- new Thread("dismissClingThread") {
- public void run() {
- SharedPreferences.Editor editor = mSharedPrefs.edit();
- editor.putBoolean(flag, true);
- editor.commit();
- }
- }.start();
- if (postAnimationCb != null) {
- postAnimationCb.run();
- }
- }
- };
- if (duration <= 0) {
- cleanUpClingCb.run();
- } else {
- cling.hide(duration, cleanUpClingCb);
- }
- mHideFromAccessibilityHelper.restoreImportantForAccessibility(mDragLayer);
-
- if (restoreNavBarVisibilty) {
- cling.setSystemUiVisibility(cling.getSystemUiVisibility() &
- ~View.SYSTEM_UI_FLAG_LOW_PROFILE);
- }
- }
- }
-
- private void removeCling(int id) {
- final View cling = findViewById(id);
- if (cling != null) {
- final ViewGroup parent = (ViewGroup) cling.getParent();
- parent.post(new Runnable() {
- @Override
- public void run() {
- parent.removeView(cling);
- }
- });
- mHideFromAccessibilityHelper.restoreImportantForAccessibility(mDragLayer);
- }
- }
-
- private boolean skipCustomClingIfNoAccounts() {
- Cling cling = (Cling) findViewById(R.id.workspace_cling);
- boolean customCling = cling.getDrawIdentifier().equals("workspace_custom");
- if (customCling) {
- AccountManager am = AccountManager.get(this);
- if (am == null) return false;
- Account[] accounts = am.getAccountsByType("com.google");
- return accounts.length == 0;
- }
- return false;
- }
-
- public void updateCustomContentHintVisibility() {
- Cling cling = (Cling) findViewById(R.id.first_run_cling);
- String ccHintStr = getFirstRunCustomContentHint();
-
- if (mWorkspace.hasCustomContent()) {
- // Show the custom content hint if ccHintStr is not empty
- if (cling != null) {
- setCustomContentHintVisibility(cling, ccHintStr, true, true);
- }
- } else {
- // Hide the custom content hint
- if (cling != null) {
- setCustomContentHintVisibility(cling, ccHintStr, false, true);
- }
- }
+ /**
+ * Called when the SearchBar hint should be changed.
+ *
+ * @param hint the hint to be displayed in the search bar.
+ */
+ protected void onSearchBarHintChanged(String hint) {
+ mLauncherClings.updateSearchBarHint(hint);
}
- private void setCustomContentHintVisibility(Cling cling, String ccHintStr, boolean visible,
- boolean animate) {
- final TextView ccHint = (TextView) cling.findViewById(R.id.custom_content_hint);
- if (ccHint != null) {
- if (visible && !ccHintStr.isEmpty()) {
- ccHint.setText(ccHintStr);
- ccHint.setVisibility(View.VISIBLE);
- if (animate) {
- ccHint.setAlpha(0f);
- ccHint.animate().alpha(1f)
- .setDuration(SHOW_CLING_DURATION)
- .start();
- } else {
- ccHint.setAlpha(1f);
- }
+ protected boolean isLauncherPreinstalled() {
+ PackageManager pm = getPackageManager();
+ try {
+ ApplicationInfo ai = pm.getApplicationInfo(getComponentName().getPackageName(), 0);
+ if ((ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ return true;
} else {
- if (animate) {
- ccHint.animate().alpha(0f)
- .setDuration(SHOW_CLING_DURATION)
- .setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- ccHint.setVisibility(View.GONE);
- }
- })
- .start();
- } else {
- ccHint.setAlpha(0f);
- ccHint.setVisibility(View.GONE);
- }
+ return false;
}
- }
- }
-
- public void showFirstRunCling() {
- if (isClingsEnabled() &&
- !mSharedPrefs.getBoolean(Cling.FIRST_RUN_CLING_DISMISSED_KEY, false) &&
- !skipCustomClingIfNoAccounts() ) {
- // If we're not using the default workspace layout, replace workspace cling
- // with a custom workspace cling (usually specified in an overlay)
- // For now, only do this on tablets
- if (!DISABLE_CUSTOM_CLINGS) {
- if (mSharedPrefs.getInt(LauncherProvider.DEFAULT_WORKSPACE_RESOURCE_ID, 0) != 0 &&
- getResources().getBoolean(R.bool.config_useCustomClings)) {
- // Use a custom cling
- View cling = findViewById(R.id.workspace_cling);
- ViewGroup clingParent = (ViewGroup) cling.getParent();
- int clingIndex = clingParent.indexOfChild(cling);
- clingParent.removeViewAt(clingIndex);
- View customCling = mInflater.inflate(R.layout.custom_workspace_cling, clingParent, false);
- clingParent.addView(customCling, clingIndex);
- customCling.setId(R.id.workspace_cling);
- }
- }
- Cling cling = (Cling) findViewById(R.id.first_run_cling);
- if (cling != null) {
- String sbHintStr = getFirstRunClingSearchBarHint();
- String ccHintStr = getFirstRunCustomContentHint();
- if (!sbHintStr.isEmpty()) {
- TextView sbHint = (TextView) cling.findViewById(R.id.search_bar_hint);
- sbHint.setText(sbHintStr);
- sbHint.setVisibility(View.VISIBLE);
- }
- setCustomContentHintVisibility(cling, ccHintStr, true, false);
- }
- initCling(R.id.first_run_cling, 0, false, true);
- } else {
- removeCling(R.id.first_run_cling);
+ } catch (NameNotFoundException e) {
+ e.printStackTrace();
+ return false;
}
}
@@ -4583,83 +4696,79 @@ public class Launcher extends Activity
return "";
}
- public void showFirstRunWorkspaceCling() {
- // Enable the clings only if they have not been dismissed before
- if (isClingsEnabled() &&
- !mSharedPrefs.getBoolean(Cling.WORKSPACE_CLING_DISMISSED_KEY, false)) {
- Cling c = initCling(R.id.workspace_cling, 0, false, true);
+ public void dismissFirstRunCling(View v) {
+ mLauncherClings.dismissFirstRunCling(v);
+ }
+ public void dismissMigrationClingCopyApps(View v) {
+ mLauncherClings.dismissMigrationClingCopyApps(v);
+ }
+ public void dismissMigrationClingUseDefault(View v) {
+ mLauncherClings.dismissMigrationClingUseDefault(v);
+ }
+ public void dismissMigrationWorkspaceCling(View v) {
+ mLauncherClings.dismissMigrationWorkspaceCling(v);
+ }
+ public void dismissWorkspaceCling(View v) {
+ mLauncherClings.dismissWorkspaceCling(v);
+ }
+ public void dismissFolderCling(View v) {
+ mLauncherClings.dismissFolderCling(v);
+ }
- // Set the focused hotseat app if there is one
- c.setFocusedHotseatApp(getFirstRunFocusedHotseatAppDrawableId(),
- getFirstRunFocusedHotseatAppRank(),
- getFirstRunFocusedHotseatAppComponentName(),
- getFirstRunFocusedHotseatAppBubbleTitle(),
- getFirstRunFocusedHotseatAppBubbleDescription());
- } else {
- removeCling(R.id.workspace_cling);
- }
+ private boolean shouldRunFirstRunActivity() {
+ return !ActivityManager.isRunningInTestHarness() &&
+ !mSharedPrefs.getBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, false);
}
- public Cling showFirstRunFoldersCling() {
- // Enable the clings only if they have not been dismissed before
- if (isClingsEnabled() &&
- !mSharedPrefs.getBoolean(Cling.FOLDER_CLING_DISMISSED_KEY, false)) {
- Cling cling = initCling(R.id.folder_cling, R.id.cling_scrim,
- true, true);
- return cling;
- } else {
- removeCling(R.id.folder_cling);
- return null;
+
+ public void showFirstRunActivity() {
+ if (shouldRunFirstRunActivity() &&
+ hasFirstRunActivity()) {
+ Intent firstRunIntent = getFirstRunActivity();
+ if (firstRunIntent != null) {
+ startActivity(firstRunIntent);
+ markFirstRunActivityShown();
+ }
}
}
- protected SharedPreferences getSharedPrefs() {
- return mSharedPrefs;
+
+ private void markFirstRunActivityShown() {
+ SharedPreferences.Editor editor = mSharedPrefs.edit();
+ editor.putBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, true);
+ editor.apply();
}
- public boolean isFolderClingVisible() {
- Cling cling = (Cling) findViewById(R.id.folder_cling);
- if (cling != null) {
- return cling.getVisibility() == View.VISIBLE;
- }
- return false;
+
+ void showWorkspaceSearchAndHotseat() {
+ if (mWorkspace != null) mWorkspace.setAlpha(1f);
+ if (mHotseat != null) mHotseat.setAlpha(1f);
+ if (mPageIndicators != null) mPageIndicators.setAlpha(1f);
+ if (mSearchDropTargetBar != null) mSearchDropTargetBar.showSearchBar(false);
}
- public void dismissFirstRunCling(View v) {
- Cling cling = (Cling) findViewById(R.id.first_run_cling);
- Runnable cb = new Runnable() {
- public void run() {
- // Show the workspace cling next
- showFirstRunWorkspaceCling();
- }
- };
- dismissCling(cling, cb, Cling.FIRST_RUN_CLING_DISMISSED_KEY,
- DISMISS_CLING_DURATION, false);
- // Fade out the search bar for the workspace cling coming up
- mSearchDropTargetBar.hideSearchBar(true);
+ 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);
}
- public void dismissWorkspaceCling(View v) {
- Cling cling = (Cling) findViewById(R.id.workspace_cling);
- Runnable cb = null;
- if (v == null) {
- cb = new Runnable() {
- public void run() {
- mWorkspace.enterOverviewMode();
- }
- };
- }
- dismissCling(cling, cb, Cling.WORKSPACE_CLING_DISMISSED_KEY,
- DISMISS_CLING_DURATION, true);
- // Fade in the search bar
- mSearchDropTargetBar.showSearchBar(true);
+
+ public ItemInfo createAppDragInfo(Intent appLaunchIntent) {
+ ResolveInfo ri = getPackageManager().resolveActivity(appLaunchIntent, 0);
+ if (ri == null) {
+ return null;
+ }
+ return new AppInfo(getPackageManager(), ri, mIconCache, null);
}
- public void dismissFolderCling(View v) {
- Cling cling = (Cling) findViewById(R.id.folder_cling);
- dismissCling(cling, null, Cling.FOLDER_CLING_DISMISSED_KEY,
- DISMISS_CLING_DURATION, true);
+
+ public ItemInfo createShortcutDragInfo(Intent shortcutIntent, CharSequence caption,
+ Bitmap icon) {
+ return new ShortcutInfo(shortcutIntent, caption, icon);
}
- public void dismissAllAppsCling(View v) {
- Cling cling = (Cling) findViewById(R.id.all_apps_cling);
- dismissCling(cling, null, Cling.ALL_APPS_CLING_DISMISSED_KEY,
- DISMISS_CLING_DURATION, true);
+
+ public void startDrag(View dragView, ItemInfo dragInfo, DragSource source) {
+ dragView.setTag(dragInfo);
+ mWorkspace.onDragStartedWithItem(dragView);
+ mWorkspace.beginDragShared(dragView, source);
}
/**
@@ -4725,22 +4834,30 @@ public class Launcher extends Activity
}
public static void addDumpLog(String tag, String log, boolean debugLog) {
+ addDumpLog(tag, log, null, debugLog);
+ }
+
+ public static void addDumpLog(String tag, String log, Exception e, boolean debugLog) {
if (debugLog) {
- Log.d(tag, log);
+ if (e != null) {
+ Log.d(tag, log, e);
+ } else {
+ Log.d(tag, log);
+ }
}
if (DEBUG_DUMP_LOG) {
sDateStamp.setTime(System.currentTimeMillis());
synchronized (sDumpLogs) {
- sDumpLogs.add(sDateFormat.format(sDateStamp) + ": " + tag + ", " + log);
+ sDumpLogs.add(sDateFormat.format(sDateStamp) + ": " + tag + ", " + log
+ + (e == null ? "" : (", Exception: " + e)));
}
}
}
public void dumpLogsToLocalData() {
if (DEBUG_DUMP_LOG) {
- new Thread("DumpLogsToLocalData") {
- @Override
- public void run() {
+ new AsyncTask<Void, Void, Void>() {
+ public Void doInBackground(Void ... args) {
boolean success = false;
sDateStamp.setTime(sRunStart);
String FILENAME = sDateStamp.getMonth() + "-"
@@ -4778,10 +4895,53 @@ public class Launcher extends Activity
} catch (IOException e) {
e.printStackTrace();
}
+ return null;
}
- }.start();
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
}
}
+
+ public AppsCustomizePagedView.SortMode getAppsCustomizeContentSortMode () {
+ return mAppsCustomizeContent.getSortMode();
+ }
+
+ public boolean shouldShowSearchBar() {
+ return mWorkspace.getShowSearchBar();
+ }
+
+ public boolean shouldHideWorkspaceIconLables() {
+ return mWorkspace.getHideIconLables();
+ }
+
+ public String getWorkspaceTransitionEffect() {
+ TransitionEffect effect = mWorkspace.getTransitionEffect();
+ return effect == null ? TransitionEffect.TRANSITION_EFFECT_NONE : effect.getName();
+ }
+
+ public String getAppsCustomizeTransitionEffect() {
+ TransitionEffect effect = mAppsCustomizeContent.getTransitionEffect();
+ return effect == null ? TransitionEffect.TRANSITION_EFFECT_NONE : effect.getName();
+ }
+
+ public void updateDynamicGrid() {
+ mSearchDropTargetBar.setupQSB(this);
+ mSearchDropTargetBar.hideSearchBar(false);
+
+ initializeDynamicGrid();
+
+ mGrid.layout(this);
+ mWorkspace.showOutlines();
+
+ // Synchronized reload
+ mModel.startLoader(true, mWorkspace.getCurrentPage());
+ mWorkspace.updateCustomContentVisibility();
+ }
+
+ public boolean isSearchBarEnabled() {
+ return SettingsProvider.getBoolean(this,
+ SettingsProvider.SETTINGS_UI_HOMESCREEN_SEARCH,
+ R.bool.preferences_interface_homescreen_search_default);
+ }
}
interface LauncherTransitionable {
@@ -4791,3 +4951,8 @@ interface LauncherTransitionable {
void onLauncherTransitionStep(Launcher l, float t);
void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace);
}
+
+interface DebugIntents {
+ static final String DELETE_DATABASE = "com.android.launcher3.action.DELETE_DATABASE";
+ static final String MIGRATE_DATABASE = "com.android.launcher3.action.MIGRATE_DATABASE";
+}
diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java
index 5d4f9c67e..e6c220b2a 100644
--- a/src/com/android/launcher3/LauncherAnimUtils.java
+++ b/src/com/android/launcher3/LauncherAnimUtils.java
@@ -25,12 +25,13 @@ import android.view.View;
import android.view.ViewTreeObserver;
import java.util.HashSet;
+import java.util.WeakHashMap;
public class LauncherAnimUtils {
- static HashSet<Animator> sAnimators = new HashSet<Animator>();
+ static WeakHashMap<Animator, Object> sAnimators = new WeakHashMap<Animator, Object>();
static Animator.AnimatorListener sEndAnimListener = new Animator.AnimatorListener() {
public void onAnimationStart(Animator animation) {
- sAnimators.add(animation);
+ sAnimators.put(animation, null);
}
public void onAnimationRepeat(Animator animation) {
@@ -74,13 +75,12 @@ public class LauncherAnimUtils {
}
public static void onDestroyActivity() {
- HashSet<Animator> animators = new HashSet<Animator>(sAnimators);
+ HashSet<Animator> animators = new HashSet<Animator>(sAnimators.keySet());
for (Animator a : animators) {
if (a.isRunning()) {
a.cancel();
- } else {
- sAnimators.remove(a);
}
+ sAnimators.remove(a);
}
}
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index cba0d61ac..11e18b186 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -22,23 +22,23 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.os.Handler;
-import android.provider.Settings;
import android.util.Log;
-import com.android.launcher3.settings.SettingsProvider;
import java.lang.ref.WeakReference;
-public class LauncherAppState {
+public class LauncherAppState implements DeviceProfile.DeviceProfileCallbacks {
private static final String TAG = "LauncherAppState";
private static final String SHARED_PREFERENCES_KEY = "com.android.launcher3.prefs";
+ private final AppFilter mAppFilter;
+ private final BuildInfo mBuildInfo;
private LauncherModel mModel;
private IconCache mIconCache;
- private AppFilter mAppFilter;
private WidgetPreviewLoader.CacheDb mWidgetPreviewCacheDb;
private boolean mIsScreenLarge;
private float mScreenDensity;
private int mLongPressTimeout = 300;
+ private boolean mWallpaperChangedSinceLastCheck;
private static WeakReference<LauncherProvider> sLauncherProvider;
private static Context sContext;
@@ -84,10 +84,11 @@ public class LauncherAppState {
mIsScreenLarge = isScreenLarge(sContext.getResources());
mScreenDensity = sContext.getResources().getDisplayMetrics().density;
- mWidgetPreviewCacheDb = new WidgetPreviewLoader.CacheDb(sContext);
+ recreateWidgetPreviewDb();
mIconCache = new IconCache(sContext);
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);
// Register intent receivers
@@ -113,18 +114,13 @@ public class LauncherAppState {
ContentResolver resolver = sContext.getContentResolver();
resolver.registerContentObserver(LauncherSettings.Favorites.CONTENT_URI, true,
mFavoritesObserver);
-
- // Generate default typeface
- String fontFamily = SettingsProvider.getString(sContext,
- SettingsProvider.SETTINGS_UI_GENERAL_ICONS_TEXT_FONT_FAMILY,
- R.string.preferences_interface_general_icons_text_font_family_default);
-
- // TODO: Implement font styles
- int fontStyle = SettingsProvider.getInt(sContext,
- SettingsProvider.SETTINGS_UI_GENERAL_ICONS_TEXT_FONT_STYLE,
- R.integer.preferences_interface_general_icons_text_font_style_default);
-
- Utilities.generateTypeface(fontFamily, fontStyle);
+ }
+
+ public void recreateWidgetPreviewDb() {
+ if (mWidgetPreviewCacheDb != null) {
+ mWidgetPreviewCacheDb.close();
+ }
+ mWidgetPreviewCacheDb = new WidgetPreviewLoader.CacheDb(sContext);
}
/**
@@ -189,21 +185,20 @@ public class LauncherAppState {
DeviceProfile initDynamicGrid(Context context, int minWidth, int minHeight,
int width, int height,
int availableWidth, int availableHeight) {
- if (mDynamicGrid == null) {
- mDynamicGrid = new DynamicGrid(context,
- context.getResources(),
- minWidth, minHeight, width, height,
- availableWidth, availableHeight);
- }
+
+ mDynamicGrid = new DynamicGrid(context,
+ context.getResources(),
+ minWidth, minHeight, width, height,
+ availableWidth, availableHeight);
+ mDynamicGrid.getDeviceProfile().addCallback(this);
// Update the icon size
DeviceProfile grid = mDynamicGrid.getDeviceProfile();
- Utilities.setIconSize(grid.iconSizePx);
- grid.updateFromConfiguration(context.getResources(), width, height,
+ grid.updateFromConfiguration(context, context.getResources(), width, height,
availableWidth, availableHeight);
return grid;
}
- DynamicGrid getDynamicGrid() {
+ public DynamicGrid getDynamicGrid() {
return mDynamicGrid;
}
@@ -228,4 +223,29 @@ public class LauncherAppState {
public int getLongPressTimeout() {
return mLongPressTimeout;
}
+
+ public void onWallpaperChanged() {
+ mWallpaperChangedSinceLastCheck = true;
+ }
+
+ public boolean hasWallpaperChangedSinceLastCheck() {
+ boolean result = mWallpaperChangedSinceLastCheck;
+ mWallpaperChangedSinceLastCheck = false;
+ return result;
+ }
+
+ @Override
+ public void onAvailableSizeChanged(DeviceProfile grid) {
+ Utilities.setIconSize(grid.iconSizePx);
+ }
+
+ public static boolean isDisableAllApps() {
+ // Returns false on non-dogfood builds.
+ return getInstance().mBuildInfo.isDogfoodBuild() &&
+ Launcher.isPropertyEnabled(Launcher.DISABLE_ALL_APPS_PROPERTY);
+ }
+
+ public static boolean isDogfoodBuild() {
+ return getInstance().mBuildInfo.isDogfoodBuild();
+ }
}
diff --git a/src/com/android/launcher3/LauncherAppWidgetHostView.java b/src/com/android/launcher3/LauncherAppWidgetHostView.java
index 83aef1a2f..51a649a07 100644
--- a/src/com/android/launcher3/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/LauncherAppWidgetHostView.java
@@ -65,6 +65,12 @@ public class LauncherAppWidgetHostView extends AppWidgetHostView implements Touc
}
public boolean onInterceptTouchEvent(MotionEvent ev) {
+ // Just in case the previous long press hasn't been cleared, we make sure to start fresh
+ // on touch down.
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ mLongPressHelper.cancelLongPress();
+ }
+
// Consume any touch events for ourselves after longpress is triggered
if (mLongPressHelper.hasPerformedLongPress()) {
mLongPressHelper.cancelLongPress();
@@ -110,13 +116,15 @@ public class LauncherAppWidgetHostView extends AppWidgetHostView implements Touc
@Override
public void onTouchComplete() {
- mLongPressHelper.cancelLongPress();
+ if (!mLongPressHelper.hasPerformedLongPress()) {
+ // If a long press has been performed, we don't want to clear the record of that since
+ // we still may be receiving a touch up which we want to intercept
+ mLongPressHelper.cancelLongPress();
+ }
}
@Override
public int getDescendantFocusability() {
return ViewGroup.FOCUS_BLOCK_DESCENDANTS;
}
-
-
}
diff --git a/src/com/android/launcher3/LauncherBackupAgentHelper.java b/src/com/android/launcher3/LauncherBackupAgentHelper.java
index 2b5059b72..de6aedddd 100644
--- a/src/com/android/launcher3/LauncherBackupAgentHelper.java
+++ b/src/com/android/launcher3/LauncherBackupAgentHelper.java
@@ -18,12 +18,22 @@ package com.android.launcher3;
import android.app.backup.BackupAgentHelper;
import android.app.backup.BackupManager;
+import android.app.backup.SharedPreferencesBackupHelper;
import android.content.Context;
+import android.content.SharedPreferences;
+import android.provider.Settings;
+import android.util.Log;
public class LauncherBackupAgentHelper extends BackupAgentHelper {
+ private static final String TAG = "LauncherBackupAgentHelper";
+ static final boolean VERBOSE = true;
+ static final boolean DEBUG = false;
+
private static BackupManager sBackupManager;
+ protected static final String SETTING_RESTORE_ENABLED = "launcher_restore_enabled";
+
/**
* Notify the backup manager that out database is dirty.
*
@@ -38,9 +48,27 @@ public class LauncherBackupAgentHelper extends BackupAgentHelper {
sBackupManager.dataChanged();
}
+ @Override
+ public void onDestroy() {
+ // There is only one process accessing this preference file, but the restore
+ // modifies the file outside the normal codepaths, so it looks like another
+ // process. This forces a reload of the file, in case this process persists.
+ String spKey = LauncherAppState.getSharedPreferencesKey();
+ SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_MULTI_PROCESS);
+ super.onDestroy();
+ }
@Override
public void onCreate() {
- addHelper(LauncherBackupHelper.LAUNCHER_PREFIX, new LauncherBackupHelper(this));
+ boolean restoreEnabled = 0 != Settings.Secure.getInt(
+ getContentResolver(), SETTING_RESTORE_ENABLED, 0);
+ if (VERBOSE) Log.v(TAG, "restore is " + (restoreEnabled ? "enabled" : "disabled"));
+
+ addHelper(LauncherBackupHelper.LAUNCHER_PREFS_PREFIX,
+ new LauncherPreferencesBackupHelper(this,
+ LauncherAppState.getSharedPreferencesKey(),
+ restoreEnabled));
+ addHelper(LauncherBackupHelper.LAUNCHER_PREFIX,
+ new LauncherBackupHelper(this, restoreEnabled));
}
}
diff --git a/src/com/android/launcher3/LauncherBackupHelper.java b/src/com/android/launcher3/LauncherBackupHelper.java
index 9b901eea1..62e6f3102 100644
--- a/src/com/android/launcher3/LauncherBackupHelper.java
+++ b/src/com/android/launcher3/LauncherBackupHelper.java
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package com.android.launcher3;
import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
@@ -31,14 +30,14 @@ import com.android.launcher3.backup.BackupProtos.Screen;
import com.android.launcher3.backup.BackupProtos.Widget;
import android.app.backup.BackupDataInputStream;
-import android.app.backup.BackupHelper;
-import android.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
+import android.app.backup.BackupHelper;
import android.app.backup.BackupManager;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.ContentResolver;
+import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
@@ -68,7 +67,8 @@ import java.util.zip.CRC32;
public class LauncherBackupHelper implements BackupHelper {
private static final String TAG = "LauncherBackupHelper";
- private static final boolean DEBUG = false;
+ private static final boolean VERBOSE = LauncherBackupAgentHelper.VERBOSE;
+ private static final boolean DEBUG = LauncherBackupAgentHelper.DEBUG;
private static final boolean DEBUG_PAYLOAD = false;
private static final int MAX_JOURNAL_SIZE = 1000000;
@@ -83,6 +83,8 @@ public class LauncherBackupHelper implements BackupHelper {
public static final String LAUNCHER_PREFIX = "L";
+ public static final String LAUNCHER_PREFS_PREFIX = "LP";
+
private static final Bitmap.CompressFormat IMAGE_FORMAT =
android.graphics.Bitmap.CompressFormat.PNG;
@@ -134,14 +136,19 @@ public class LauncherBackupHelper implements BackupHelper {
private static final int SCREEN_RANK_INDEX = 2;
+ private static IconCache mIconCache;
+
private final Context mContext;
+ private final boolean mRestoreEnabled;
+
private HashMap<ComponentName, AppWidgetProviderInfo> mWidgetMap;
private ArrayList<Key> mKeys;
- public LauncherBackupHelper(Context context) {
+ public LauncherBackupHelper(Context context, boolean restoreEnabled) {
mContext = context;
+ mRestoreEnabled = restoreEnabled;
}
private void dataChanged() {
@@ -166,7 +173,7 @@ public class LauncherBackupHelper implements BackupHelper {
@Override
public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
ParcelFileDescriptor newState) {
- Log.v(TAG, "onBackup");
+ if (VERBOSE) Log.v(TAG, "onBackup");
Journal in = readJournal(oldState);
Journal out = new Journal();
@@ -176,19 +183,23 @@ public class LauncherBackupHelper implements BackupHelper {
out.rows = 0;
out.bytes = 0;
- Log.v(TAG, "lastBackupTime=" + lastBackupTime);
+ Log.v(TAG, "lastBackupTime = " + lastBackupTime);
ArrayList<Key> keys = new ArrayList<Key>();
- try {
- backupFavorites(in, data, out, keys);
- backupScreens(in, data, out, keys);
- backupIcons(in, data, out, keys);
- backupWidgets(in, data, out, keys);
- } catch (IOException e) {
- Log.e(TAG, "launcher backup has failed", e);
+ if (launcherIsReady()) {
+ try {
+ backupFavorites(in, data, out, keys);
+ backupScreens(in, data, out, keys);
+ backupIcons(in, data, out, keys);
+ backupWidgets(in, data, out, keys);
+ } catch (IOException e) {
+ Log.e(TAG, "launcher backup has failed", e);
+ }
+ out.key = keys.toArray(new BackupProtos.Key[keys.size()]);
+ } else {
+ out = in;
}
- out.key = keys.toArray(BackupProtos.Key.EMPTY_ARRAY);
writeJournal(newState, out);
Log.v(TAG, "onBackup: wrote " + out.bytes + "b in " + out.rows + " rows.");
}
@@ -202,7 +213,7 @@ public class LauncherBackupHelper implements BackupHelper {
*/
@Override
public void restoreEntity(BackupDataInputStream data) {
- Log.v(TAG, "restoreEntity");
+ if (VERBOSE) Log.v(TAG, "restoreEntity");
if (mKeys == null) {
mKeys = new ArrayList<Key>();
}
@@ -218,10 +229,11 @@ public class LauncherBackupHelper implements BackupHelper {
bytesRead = data.read(buffer, 0, dataSize);
if (DEBUG) Log.d(TAG, "read " + bytesRead + " of " + dataSize + " available");
} catch (IOException e) {
- Log.d(TAG, "failed to read entity from restore data", e);
+ Log.e(TAG, "failed to read entity from restore data", e);
}
try {
key = backupKeyToKey(backupKey);
+ mKeys.add(key);
switch (key.type) {
case Key.FAVORITE:
restoreFavorite(key, buffer, dataSize, mKeys);
@@ -260,7 +272,7 @@ public class LauncherBackupHelper implements BackupHelper {
// will catch any changes the restore process might have made
Journal out = new Journal();
out.t = 0;
- out.key = mKeys.toArray(BackupProtos.Key.EMPTY_ARRAY);
+ out.key = mKeys.toArray(new BackupProtos.Key[mKeys.size()]);
writeJournal(newState, out);
Log.v(TAG, "onRestore: read " + mKeys.size() + " rows");
mKeys.clear();
@@ -295,10 +307,13 @@ public class LauncherBackupHelper implements BackupHelper {
final long updateTime = cursor.getLong(ID_MODIFIED);
Key key = getKey(Key.FAVORITE, id);
keys.add(key);
- currentIds.add(keyToBackupKey(key));
- if (updateTime > in.t) {
+ final String backupKey = keyToBackupKey(key);
+ currentIds.add(backupKey);
+ if (!savedIds.contains(backupKey) || updateTime >= in.t) {
byte[] blob = packFavorite(cursor);
writeRowToBackup(key, blob, out, data);
+ } else {
+ if (VERBOSE) Log.v(TAG, "favorite " + id + " was too old: " + updateTime);
}
}
} finally {
@@ -322,15 +337,21 @@ public class LauncherBackupHelper implements BackupHelper {
* @param keys keys to mark as clean in the notes for next backup
*/
private void restoreFavorite(Key key, byte[] buffer, int dataSize, ArrayList<Key> keys) {
- Log.v(TAG, "unpacking favorite " + key.id + " (" + dataSize + " bytes)");
+ if (VERBOSE) Log.v(TAG, "unpacking favorite " + key.id);
if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
+ if (!mRestoreEnabled) {
+ if (VERBOSE) Log.v(TAG, "restore not enabled: skipping database mutation");
+ return;
+ }
+
try {
- Favorite favorite = unpackFavorite(buffer, 0, dataSize);
- if (DEBUG) Log.d(TAG, "unpacked " + favorite.itemType);
+ ContentResolver cr = mContext.getContentResolver();
+ ContentValues values = unpackFavorite(buffer, 0, dataSize);
+ cr.insert(Favorites.CONTENT_URI, values);
} catch (InvalidProtocolBufferNanoException e) {
- Log.w(TAG, "failed to decode proto", e);
+ Log.e(TAG, "failed to decode favorite", e);
}
}
@@ -358,15 +379,19 @@ public class LauncherBackupHelper implements BackupHelper {
Set<String> currentIds = new HashSet<String>(cursor.getCount());
try {
cursor.moveToPosition(-1);
+ if (DEBUG) Log.d(TAG, "dumping screens after: " + in.t);
while(cursor.moveToNext()) {
final long id = cursor.getLong(ID_INDEX);
final long updateTime = cursor.getLong(ID_MODIFIED);
Key key = getKey(Key.SCREEN, id);
keys.add(key);
- currentIds.add(keyToBackupKey(key));
- if (updateTime > in.t) {
+ final String backupKey = keyToBackupKey(key);
+ currentIds.add(backupKey);
+ if (!savedIds.contains(backupKey) || updateTime >= in.t) {
byte[] blob = packScreen(cursor);
writeRowToBackup(key, blob, out, data);
+ } else {
+ if (VERBOSE) Log.v(TAG, "screen " + id + " was too old: " + updateTime);
}
}
} finally {
@@ -390,14 +415,22 @@ public class LauncherBackupHelper implements BackupHelper {
* @param keys keys to mark as clean in the notes for next backup
*/
private void restoreScreen(Key key, byte[] buffer, int dataSize, ArrayList<Key> keys) {
- Log.v(TAG, "unpacking screen " + key.id);
+ if (VERBOSE) Log.v(TAG, "unpacking screen " + key.id);
if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
+
+ if (!mRestoreEnabled) {
+ if (VERBOSE) Log.v(TAG, "restore not enabled: skipping database mutation");
+ return;
+ }
+
try {
- Screen screen = unpackScreen(buffer, 0, dataSize);
- if (DEBUG) Log.d(TAG, "unpacked " + screen.rank);
+ ContentResolver cr = mContext.getContentResolver();
+ ContentValues values = unpackScreen(buffer, 0, dataSize);
+ cr.insert(WorkspaceScreens.CONTENT_URI, values);
+
} catch (InvalidProtocolBufferNanoException e) {
- Log.w(TAG, "failed to decode proto", e);
+ Log.e(TAG, "failed to decode screen", e);
}
}
@@ -414,14 +447,12 @@ public class LauncherBackupHelper implements BackupHelper {
private void backupIcons(Journal in, BackupDataOutput data, Journal out,
ArrayList<Key> keys) throws IOException {
// persist icons that haven't been persisted yet
- final LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
- if (appState == null) {
+ if (!initializeIconCache()) {
dataChanged(); // try again later
if (DEBUG) Log.d(TAG, "Launcher is not initialized, delaying icon backup");
return;
}
final ContentResolver cr = mContext.getContentResolver();
- final IconCache iconCache = appState.getIconCache();
final int dpi = mContext.getResources().getDisplayMetrics().densityDpi;
// read the old ID set
@@ -452,30 +483,30 @@ public class LauncherBackupHelper implements BackupHelper {
Log.w(TAG, "empty intent on application favorite: " + id);
}
if (savedIds.contains(backupKey)) {
- if (DEBUG) Log.d(TAG, "already saved icon " + backupKey);
+ if (VERBOSE) Log.v(TAG, "already saved icon " + backupKey);
// remember that we already backed this up previously
keys.add(key);
} else if (backupKey != null) {
if (DEBUG) Log.d(TAG, "I can count this high: " + out.rows);
if ((out.rows - startRows) < MAX_ICONS_PER_PASS) {
- if (DEBUG) Log.d(TAG, "saving icon " + backupKey);
- Bitmap icon = iconCache.getIcon(intent);
+ if (VERBOSE) Log.v(TAG, "saving icon " + backupKey);
+ Bitmap icon = mIconCache.getIcon(intent);
keys.add(key);
- if (icon != null && !iconCache.isDefaultIcon(icon)) {
+ if (icon != null && !mIconCache.isDefaultIcon(icon)) {
byte[] blob = packIcon(dpi, icon);
writeRowToBackup(key, blob, out, data);
}
} else {
- if (DEBUG) Log.d(TAG, "scheduling another run for icon " + backupKey);
+ if (VERBOSE) Log.d(TAG, "deferring icon backup " + backupKey);
// too many icons for this pass, request another.
dataChanged();
}
}
} catch (URISyntaxException e) {
- Log.w(TAG, "invalid URI on application favorite: " + id);
+ Log.e(TAG, "invalid URI on application favorite: " + id);
} catch (IOException e) {
- Log.w(TAG, "unable to save application icon for favorite: " + id);
+ Log.e(TAG, "unable to save application icon for favorite: " + id);
}
}
@@ -500,21 +531,36 @@ public class LauncherBackupHelper implements BackupHelper {
* @param keys keys to mark as clean in the notes for next backup
*/
private void restoreIcon(Key key, byte[] buffer, int dataSize, ArrayList<Key> keys) {
- Log.v(TAG, "unpacking icon " + key.id);
+ if (VERBOSE) Log.v(TAG, "unpacking icon " + key.id);
if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
+
try {
Resource res = unpackIcon(buffer, 0, dataSize);
- if (DEBUG) Log.d(TAG, "unpacked " + res.dpi);
- if (DEBUG) Log.d(TAG, "read " +
- Base64.encodeToString(res.data, 0, res.data.length,
- Base64.NO_WRAP));
+ if (DEBUG) {
+ Log.d(TAG, "unpacked " + res.dpi + " dpi icon");
+ }
+ if (DEBUG_PAYLOAD) {
+ Log.d(TAG, "read " +
+ Base64.encodeToString(res.data, 0, res.data.length,
+ Base64.NO_WRAP));
+ }
Bitmap icon = BitmapFactory.decodeByteArray(res.data, 0, res.data.length);
if (icon == null) {
Log.w(TAG, "failed to unpack icon for " + key.name);
}
- } catch (InvalidProtocolBufferNanoException e) {
- Log.w(TAG, "failed to decode proto", e);
+
+ if (!mRestoreEnabled) {
+ if (VERBOSE) {
+ Log.v(TAG, "restore not enabled: skipping database mutation");
+ }
+ return;
+ } else {
+ IconCache.preloadIcon(mContext, ComponentName.unflattenFromString(key.name),
+ icon, res.dpi);
+ }
+ } catch (IOException e) {
+ Log.d(TAG, "failed to save restored icon for: " + key.name, e);
}
}
@@ -532,15 +578,13 @@ public class LauncherBackupHelper implements BackupHelper {
ArrayList<Key> keys) throws IOException {
// persist static widget info that hasn't been persisted yet
final LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
- if (appState == null) {
- dataChanged(); // try again later
- if (DEBUG) Log.d(TAG, "Launcher is not initialized, delaying widget backup");
+ if (appState == null || !initializeIconCache()) {
+ Log.w(TAG, "Failed to get icon cache during restore");
return;
}
final ContentResolver cr = mContext.getContentResolver();
final WidgetPreviewLoader previewLoader = new WidgetPreviewLoader(mContext);
final PagedViewCellLayout widgetSpacingLayout = new PagedViewCellLayout(mContext);
- final IconCache iconCache = appState.getIconCache();
final int dpi = mContext.getResources().getDisplayMetrics().densityDpi;
final DeviceProfile profile = appState.getDynamicGrid().getDeviceProfile();
if (DEBUG) Log.d(TAG, "cellWidthPx: " + profile.cellWidthPx);
@@ -573,22 +617,22 @@ public class LauncherBackupHelper implements BackupHelper {
Log.w(TAG, "empty intent on appwidget: " + id);
}
if (savedIds.contains(backupKey)) {
- if (DEBUG) Log.d(TAG, "already saved widget " + backupKey);
+ if (VERBOSE) Log.v(TAG, "already saved widget " + backupKey);
// remember that we already backed this up previously
keys.add(key);
} else if (backupKey != null) {
if (DEBUG) Log.d(TAG, "I can count this high: " + out.rows);
if ((out.rows - startRows) < MAX_WIDGETS_PER_PASS) {
- if (DEBUG) Log.d(TAG, "saving widget " + backupKey);
+ if (VERBOSE) Log.v(TAG, "saving widget " + backupKey);
previewLoader.setPreviewSize(spanX * profile.cellWidthPx,
spanY * profile.cellHeightPx, widgetSpacingLayout);
- byte[] blob = packWidget(dpi, previewLoader, iconCache, provider);
+ byte[] blob = packWidget(dpi, previewLoader, mIconCache, provider);
keys.add(key);
writeRowToBackup(key, blob, out, data);
} else {
- if (DEBUG) Log.d(TAG, "scheduling another run for widget " + backupKey);
+ if (VERBOSE) Log.d(TAG, "deferring widget backup " + backupKey);
// too many widgets for this pass, request another.
dataChanged();
}
@@ -615,7 +659,7 @@ public class LauncherBackupHelper implements BackupHelper {
* @param keys keys to mark as clean in the notes for next backup
*/
private void restoreWidget(Key key, byte[] buffer, int dataSize, ArrayList<Key> keys) {
- Log.v(TAG, "unpacking widget " + key.id);
+ if (VERBOSE) Log.v(TAG, "unpacking widget " + key.id);
if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
try {
@@ -628,8 +672,15 @@ public class LauncherBackupHelper implements BackupHelper {
Log.w(TAG, "failed to unpack widget icon for " + key.name);
}
}
+
+ if (!mRestoreEnabled) {
+ if (VERBOSE) Log.v(TAG, "restore not enabled: skipping database mutation");
+ return;
+ } else {
+ // future site of widget table mutation
+ }
} catch (InvalidProtocolBufferNanoException e) {
- Log.w(TAG, "failed to decode proto", e);
+ Log.e(TAG, "failed to decode widget", e);
}
}
@@ -764,11 +815,48 @@ public class LauncherBackupHelper implements BackupHelper {
}
/** Deserialize a Favorite from persistence, after verifying checksum wrapper. */
- private Favorite unpackFavorite(byte[] buffer, int offset, int dataSize)
+ private ContentValues unpackFavorite(byte[] buffer, int offset, int dataSize)
throws InvalidProtocolBufferNanoException {
Favorite favorite = new Favorite();
MessageNano.mergeFrom(favorite, readCheckedBytes(buffer, offset, dataSize));
- return favorite;
+ if (VERBOSE) Log.v(TAG, "unpacked favorite " + favorite.itemType + ", " +
+ (TextUtils.isEmpty(favorite.title) ? favorite.id : favorite.title));
+ ContentValues values = new ContentValues();
+ values.put(Favorites._ID, favorite.id);
+ values.put(Favorites.SCREEN, favorite.screen);
+ values.put(Favorites.CONTAINER, favorite.container);
+ values.put(Favorites.CELLX, favorite.cellX);
+ values.put(Favorites.CELLY, favorite.cellY);
+ values.put(Favorites.SPANX, favorite.spanX);
+ values.put(Favorites.SPANY, favorite.spanY);
+ values.put(Favorites.ICON_TYPE, favorite.iconType);
+ if (favorite.iconType == Favorites.ICON_TYPE_RESOURCE) {
+ values.put(Favorites.ICON_PACKAGE, favorite.iconPackage);
+ values.put(Favorites.ICON_RESOURCE, favorite.iconResource);
+ }
+ if (favorite.iconType == Favorites.ICON_TYPE_BITMAP) {
+ values.put(Favorites.ICON, favorite.icon);
+ }
+ if (!TextUtils.isEmpty(favorite.title)) {
+ values.put(Favorites.TITLE, favorite.title);
+ } else {
+ values.put(Favorites.TITLE, "");
+ }
+ if (!TextUtils.isEmpty(favorite.intent)) {
+ values.put(Favorites.INTENT, favorite.intent);
+ }
+ values.put(Favorites.ITEM_TYPE, favorite.itemType);
+ if (favorite.itemType == Favorites.ITEM_TYPE_APPWIDGET) {
+ if (!TextUtils.isEmpty(favorite.appWidgetProvider)) {
+ values.put(Favorites.APPWIDGET_PROVIDER, favorite.appWidgetProvider);
+ }
+ values.put(Favorites.APPWIDGET_ID, favorite.appWidgetId);
+ }
+
+ // Let LauncherModel know we've been here.
+ values.put(LauncherSettings.Favorites.RESTORED, 1);
+
+ return values;
}
/** Serialize a Screen for persistence, including a checksum wrapper. */
@@ -781,11 +869,15 @@ public class LauncherBackupHelper implements BackupHelper {
}
/** Deserialize a Screen from persistence, after verifying checksum wrapper. */
- private Screen unpackScreen(byte[] buffer, int offset, int dataSize)
+ private ContentValues unpackScreen(byte[] buffer, int offset, int dataSize)
throws InvalidProtocolBufferNanoException {
Screen screen = new Screen();
MessageNano.mergeFrom(screen, readCheckedBytes(buffer, offset, dataSize));
- return screen;
+ if (VERBOSE) Log.v(TAG, "unpacked screen " + screen.id + "/" + screen.rank);
+ ContentValues values = new ContentValues();
+ values.put(WorkspaceScreens._ID, screen.id);
+ values.put(WorkspaceScreens.SCREEN_RANK, screen.rank);
+ return values;
}
/** Serialize an icon Resource for persistence, including a checksum wrapper. */
@@ -800,10 +892,11 @@ public class LauncherBackupHelper implements BackupHelper {
}
/** Deserialize an icon resource from persistence, after verifying checksum wrapper. */
- private Resource unpackIcon(byte[] buffer, int offset, int dataSize)
+ private static Resource unpackIcon(byte[] buffer, int offset, int dataSize)
throws InvalidProtocolBufferNanoException {
Resource res = new Resource();
MessageNano.mergeFrom(res, readCheckedBytes(buffer, offset, dataSize));
+ if (VERBOSE) Log.v(TAG, "unpacked icon " + res.dpi + "/" + res.data.length);
return res;
}
@@ -842,6 +935,7 @@ public class LauncherBackupHelper implements BackupHelper {
throws InvalidProtocolBufferNanoException {
Widget widget = new Widget();
MessageNano.mergeFrom(widget, readCheckedBytes(buffer, offset, dataSize));
+ if (VERBOSE) Log.v(TAG, "unpacked widget " + widget.provider);
return widget;
}
@@ -852,7 +946,7 @@ public class LauncherBackupHelper implements BackupHelper {
* in that case, do a full backup.
*
* @param oldState the read-0only file descriptor pointing to the old journal
- * @return a Journal protocol bugffer
+ * @return a Journal protocol buffer
*/
private Journal readJournal(ParcelFileDescriptor oldState) {
Journal journal = new Journal();
@@ -861,47 +955,61 @@ public class LauncherBackupHelper implements BackupHelper {
}
FileInputStream inStream = new FileInputStream(oldState.getFileDescriptor());
try {
- int remaining = inStream.available();
- if (DEBUG) Log.d(TAG, "available " + remaining);
- if (remaining < MAX_JOURNAL_SIZE) {
- byte[] buffer = new byte[remaining];
+ int availableBytes = inStream.available();
+ if (DEBUG) Log.d(TAG, "available " + availableBytes);
+ if (availableBytes < MAX_JOURNAL_SIZE) {
+ byte[] buffer = new byte[availableBytes];
int bytesRead = 0;
- while (remaining > 0) {
+ boolean valid = false;
+ InvalidProtocolBufferNanoException lastProtoException = null;
+ while (availableBytes > 0) {
try {
- int result = inStream.read(buffer, bytesRead, remaining);
+ // OMG what are you doing? This is crazy inefficient!
+ // If we read a byte that is not ours, we will cause trouble: b/12491813
+ // However, we don't know how many bytes to expect (oops).
+ // So we have to step through *slowly*, watching for the end.
+ int result = inStream.read(buffer, bytesRead, 1);
if (result > 0) {
- if (DEBUG) Log.d(TAG, "read some bytes: " + result);
- remaining -= result;
+ availableBytes -= result;
bytesRead += result;
+ if (DEBUG && (bytesRead % 100 == 0)) {
+ Log.d(TAG, "read some bytes: " + bytesRead);
+ }
} else {
- // stop reading ands see what there is to parse
- Log.w(TAG, "read error: " + result);
- remaining = 0;
+ Log.w(TAG, "unexpected end of file while reading journal.");
+ // stop reading and see what there is to parse
+ availableBytes = 0;
}
} catch (IOException e) {
- Log.w(TAG, "failed to read the journal", e);
buffer = null;
- remaining = 0;
+ availableBytes = 0;
}
- }
- if (DEBUG) Log.d(TAG, "journal bytes read: " + bytesRead);
- if (buffer != null) {
+ // check the buffer to see if we have a valid journal
try {
MessageNano.mergeFrom(journal, readCheckedBytes(buffer, 0, bytesRead));
+ // if we are here, then we have read a valid, checksum-verified journal
+ valid = true;
+ availableBytes = 0;
+ if (VERBOSE) Log.v(TAG, "read " + bytesRead + " bytes of journal");
} catch (InvalidProtocolBufferNanoException e) {
- Log.d(TAG, "failed to read the journal", e);
+ // if we don't have the whole journal yet, mergeFrom will throw. keep going.
+ lastProtoException = e;
journal.clear();
}
}
+ if (DEBUG) Log.d(TAG, "journal bytes read: " + bytesRead);
+ if (!valid) {
+ Log.w(TAG, "could not find a valid journal", lastProtoException);
+ }
}
} catch (IOException e) {
- Log.d(TAG, "failed to close the journal", e);
+ Log.w(TAG, "failed to close the journal", e);
} finally {
try {
inStream.close();
} catch (IOException e) {
- Log.d(TAG, "failed to close the journal", e);
+ Log.w(TAG, "failed to close the journal", e);
}
}
return journal;
@@ -914,7 +1022,7 @@ public class LauncherBackupHelper implements BackupHelper {
data.writeEntityData(blob, blob.length);
out.rows++;
out.bytes += blob.length;
- Log.v(TAG, "saving " + geKeyType(key) + " " + backupKey + ": " +
+ if (VERBOSE) Log.v(TAG, "saving " + geKeyType(key) + " " + backupKey + ": " +
getKeyName(key) + "/" + blob.length);
if(DEBUG_PAYLOAD) {
String encoded = Base64.encodeToString(blob, 0, blob.length, Base64.NO_WRAP);
@@ -922,7 +1030,7 @@ public class LauncherBackupHelper implements BackupHelper {
for (int offset = 0; offset < encoded.length(); offset += chunkSize) {
int end = offset + chunkSize;
end = Math.min(end, encoded.length());
- Log.d(TAG, "wrote " + encoded.substring(offset, end));
+ Log.w(TAG, "wrote " + encoded.substring(offset, end));
}
}
}
@@ -942,7 +1050,7 @@ public class LauncherBackupHelper implements BackupHelper {
throws IOException {
int rows = 0;
for(String deleted: deletedIds) {
- Log.v(TAG, "dropping icon " + deleted);
+ if (VERBOSE) Log.v(TAG, "dropping deleted item " + deleted);
data.writeEntityHeader(deleted, -1);
rows++;
}
@@ -962,10 +1070,12 @@ public class LauncherBackupHelper implements BackupHelper {
FileOutputStream outStream = null;
try {
outStream = new FileOutputStream(newState.getFileDescriptor());
- outStream.write(writeCheckedBytes(journal));
+ final byte[] journalBytes = writeCheckedBytes(journal);
+ outStream.write(journalBytes);
outStream.close();
+ if (VERBOSE) Log.v(TAG, "wrote " + journalBytes.length + " bytes of journal");
} catch (IOException e) {
- Log.d(TAG, "failed to write backup journal", e);
+ Log.w(TAG, "failed to write backup journal", e);
}
}
@@ -980,7 +1090,7 @@ public class LauncherBackupHelper implements BackupHelper {
}
/** Unwrap a proto message from a CheckedMessage, verifying the checksum. */
- private byte[] readCheckedBytes(byte[] buffer, int offset, int dataSize)
+ private static byte[] readCheckedBytes(byte[] buffer, int offset, int dataSize)
throws InvalidProtocolBufferNanoException {
CheckedMessage wrapper = new CheckedMessage();
MessageNano.mergeFrom(wrapper, buffer, offset, dataSize);
@@ -1004,6 +1114,43 @@ public class LauncherBackupHelper implements BackupHelper {
return mWidgetMap.get(component);
}
+
+ private boolean initializeIconCache() {
+ if (mIconCache != null) {
+ return true;
+ }
+
+ final LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
+ if (appState == null) {
+ Throwable stackTrace = new Throwable();
+ stackTrace.fillInStackTrace();
+ Log.w(TAG, "Failed to get app state during backup/restore", stackTrace);
+ return false;
+ }
+ mIconCache = appState.getIconCache();
+ return mIconCache != null;
+ }
+
+
+ // check if the launcher is in a state to support backup
+ private boolean launcherIsReady() {
+ ContentResolver cr = mContext.getContentResolver();
+ Cursor cursor = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION, null, null, null);
+ if (cursor == null) {
+ // launcher data has been wiped, do nothing
+ return false;
+ }
+ cursor.close();
+
+ if (!initializeIconCache()) {
+ // launcher services are unavailable, try again later
+ dataChanged();
+ return false;
+ }
+
+ return true;
+ }
+
private class KeyParsingException extends Throwable {
private KeyParsingException(Throwable cause) {
super(cause);
diff --git a/src/com/android/launcher3/LauncherClings.java b/src/com/android/launcher3/LauncherClings.java
new file mode 100644
index 000000000..e5e0c1621
--- /dev/null
+++ b/src/com/android/launcher3/LauncherClings.java
@@ -0,0 +1,451 @@
+/*
+ * Copyright (C) 2008 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.accounts.Account;
+import android.accounts.AccountManager;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.UserManager;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityManager;
+import android.widget.TextView;
+
+class LauncherClings {
+ private static final String FIRST_RUN_CLING_DISMISSED_KEY = "cling_gel.first_run.dismissed";
+ private static final String MIGRATION_CLING_DISMISSED_KEY = "cling_gel.migration.dismissed";
+ private static final String MIGRATION_WORKSPACE_CLING_DISMISSED_KEY =
+ "cling_gel.migration_workspace.dismissed";
+ private static final String WORKSPACE_CLING_DISMISSED_KEY = "cling_gel.workspace.dismissed";
+ private static final String FOLDER_CLING_DISMISSED_KEY = "cling_gel.folder.dismissed";
+
+ private static final boolean DISABLE_CLINGS = false;
+
+ private static final int SHOW_CLING_DURATION = 250;
+ private static final int DISMISS_CLING_DURATION = 200;
+
+ private Launcher mLauncher;
+ private LayoutInflater mInflater;
+ private HideFromAccessibilityHelper mHideFromAccessibilityHelper
+ = new HideFromAccessibilityHelper();
+
+ /** Ctor */
+ public LauncherClings(Launcher launcher) {
+ mLauncher = launcher;
+ mInflater = mLauncher.getLayoutInflater();
+ }
+
+ /** Initializes a cling */
+ private Cling initCling(int clingId, int scrimId, boolean animate,
+ boolean dimNavBarVisibilty) {
+ Cling cling = (Cling) mLauncher.findViewById(clingId);
+ View scrim = null;
+ if (scrimId > 0) {
+ scrim = mLauncher.findViewById(scrimId);
+ }
+ if (cling != null) {
+ cling.init(mLauncher, scrim);
+ cling.show(animate, SHOW_CLING_DURATION);
+
+ if (dimNavBarVisibilty) {
+ cling.setSystemUiVisibility(cling.getSystemUiVisibility() |
+ View.SYSTEM_UI_FLAG_LOW_PROFILE);
+ }
+ }
+ return cling;
+ }
+
+ /** Returns whether the clings are enabled or should be shown */
+ private boolean areClingsEnabled() {
+ if (DISABLE_CLINGS) {
+ return false;
+ }
+
+ // disable clings when running in a test harness
+ if(ActivityManager.isRunningInTestHarness()) return false;
+
+ // Disable clings for accessibility when explore by touch is enabled
+ final AccessibilityManager a11yManager = (AccessibilityManager) mLauncher.getSystemService(
+ Launcher.ACCESSIBILITY_SERVICE);
+ if (a11yManager.isTouchExplorationEnabled()) {
+ return false;
+ }
+
+ // 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) {
+ UserManager um = (UserManager) mLauncher.getSystemService(Context.USER_SERVICE);
+ Bundle restrictions = um.getUserRestrictions();
+ if (restrictions.getBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS, false)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /** Returns whether the folder cling is visible. */
+ public boolean isFolderClingVisible() {
+ Cling cling = (Cling) mLauncher.findViewById(R.id.folder_cling);
+ if (cling != null) {
+ return cling.getVisibility() == View.VISIBLE;
+ }
+ return false;
+ }
+
+ private boolean skipCustomClingIfNoAccounts() {
+ Cling cling = (Cling) mLauncher.findViewById(R.id.workspace_cling);
+ boolean customCling = cling.getDrawIdentifier().equals("workspace_custom");
+ if (customCling) {
+ AccountManager am = AccountManager.get(mLauncher);
+ if (am == null) return false;
+ Account[] accounts = am.getAccountsByType("com.google");
+ return accounts.length == 0;
+ }
+ return false;
+ }
+
+ /** Updates the first run cling custom content hint */
+ private void setCustomContentHintVisibility(Cling cling, String ccHintStr, boolean visible,
+ boolean animate) {
+ final TextView ccHint = (TextView) cling.findViewById(R.id.custom_content_hint);
+ if (ccHint != null) {
+ if (visible && !ccHintStr.isEmpty()) {
+ ccHint.setText(ccHintStr);
+ ccHint.setVisibility(View.VISIBLE);
+ if (animate) {
+ ccHint.setAlpha(0f);
+ ccHint.animate().alpha(1f)
+ .setDuration(SHOW_CLING_DURATION)
+ .start();
+ } else {
+ ccHint.setAlpha(1f);
+ }
+ } else {
+ if (animate) {
+ ccHint.animate().alpha(0f)
+ .setDuration(SHOW_CLING_DURATION)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ ccHint.setVisibility(View.GONE);
+ }
+ })
+ .start();
+ } else {
+ ccHint.setAlpha(0f);
+ ccHint.setVisibility(View.GONE);
+ }
+ }
+ }
+ }
+
+ /** Updates the first run cling custom content hint */
+ public void updateCustomContentHintVisibility() {
+ Cling cling = (Cling) mLauncher.findViewById(R.id.first_run_cling);
+ String ccHintStr = mLauncher.getFirstRunCustomContentHint();
+
+ if (mLauncher.getWorkspace().hasCustomContent()) {
+ // Show the custom content hint if ccHintStr is not empty
+ if (cling != null) {
+ setCustomContentHintVisibility(cling, ccHintStr, true, true);
+ }
+ } else {
+ // Hide the custom content hint
+ if (cling != null) {
+ setCustomContentHintVisibility(cling, ccHintStr, false, true);
+ }
+ }
+ }
+
+ /** Updates the first run cling search bar hint. */
+ public void updateSearchBarHint(String hint) {
+ Cling cling = (Cling) mLauncher.findViewById(R.id.first_run_cling);
+ if (cling != null && cling.getVisibility() == View.VISIBLE && !hint.isEmpty()) {
+ TextView sbHint = (TextView) cling.findViewById(R.id.search_bar_hint);
+ sbHint.setText(hint);
+ sbHint.setVisibility(View.VISIBLE);
+ }
+ }
+
+ public boolean shouldShowFirstRunOrMigrationClings() {
+ SharedPreferences sharedPrefs = mLauncher.getSharedPrefs();
+ return areClingsEnabled() &&
+ !sharedPrefs.getBoolean(FIRST_RUN_CLING_DISMISSED_KEY, false) &&
+ !sharedPrefs.getBoolean(MIGRATION_CLING_DISMISSED_KEY, false);
+ }
+
+ public void removeFirstRunAndMigrationClings() {
+ removeCling(R.id.first_run_cling);
+ removeCling(R.id.migration_cling);
+ }
+
+ /**
+ * Shows the first run cling.
+ *
+ * This flow is mutually exclusive with showMigrationCling, and only runs if this Launcher
+ * package was preinstalled or there is no db to migrate from.
+ */
+ public void showFirstRunCling() {
+ if (!skipCustomClingIfNoAccounts()) {
+ Cling cling = (Cling) mLauncher.findViewById(R.id.first_run_cling);
+ if (cling != null) {
+ String sbHintStr = mLauncher.getFirstRunClingSearchBarHint();
+ String ccHintStr = mLauncher.getFirstRunCustomContentHint();
+ if (!sbHintStr.isEmpty()) {
+ TextView sbHint = (TextView) cling.findViewById(R.id.search_bar_hint);
+ sbHint.setText(sbHintStr);
+ sbHint.setVisibility(View.VISIBLE);
+ }
+ setCustomContentHintVisibility(cling, ccHintStr, true, false);
+ }
+ initCling(R.id.first_run_cling, 0, false, true);
+ } else {
+ removeFirstRunAndMigrationClings();
+ }
+ }
+
+ /**
+ * Shows the migration cling.
+ *
+ * This flow is mutually exclusive with showFirstRunCling, and only runs if this Launcher
+ * package was not preinstalled and there exists a db to migrate from.
+ */
+ public void showMigrationCling() {
+ mLauncher.hideWorkspaceSearchAndHotseat();
+
+ Cling c = initCling(R.id.migration_cling, 0, false, true);
+ c.bringScrimToFront();
+ c.bringToFront();
+ }
+
+ public void showMigrationWorkspaceCling() {
+ // Enable the clings only if they have not been dismissed before
+ if (areClingsEnabled() && !mLauncher.getSharedPrefs().getBoolean(
+ MIGRATION_WORKSPACE_CLING_DISMISSED_KEY, false)) {
+ Cling c = initCling(R.id.migration_workspace_cling, 0, false, true);
+ c.updateMigrationWorkspaceBubblePosition();
+ c.bringScrimToFront();
+ c.bringToFront();
+ } else {
+ removeCling(R.id.migration_workspace_cling);
+ }
+ }
+
+ public void showWorkspaceCling() {
+ // Enable the clings only if they have not been dismissed before
+ if (areClingsEnabled() && !mLauncher.getSharedPrefs().getBoolean(
+ WORKSPACE_CLING_DISMISSED_KEY, false)) {
+ Cling c = initCling(R.id.workspace_cling, 0, false, true);
+ c.updateWorkspaceBubblePosition();
+
+ // Set the focused hotseat app if there is one
+ c.setFocusedHotseatApp(mLauncher.getFirstRunFocusedHotseatAppDrawableId(),
+ mLauncher.getFirstRunFocusedHotseatAppRank(),
+ mLauncher.getFirstRunFocusedHotseatAppComponentName(),
+ mLauncher.getFirstRunFocusedHotseatAppBubbleTitle(),
+ mLauncher.getFirstRunFocusedHotseatAppBubbleDescription());
+ } else {
+ removeCling(R.id.workspace_cling);
+ }
+ }
+
+ public Cling showFoldersCling() {
+ SharedPreferences sharedPrefs = mLauncher.getSharedPrefs();
+ // Enable the clings only if they have not been dismissed before
+ if (areClingsEnabled() &&
+ !sharedPrefs.getBoolean(FOLDER_CLING_DISMISSED_KEY, false) &&
+ !sharedPrefs.getBoolean(Launcher.USER_HAS_MIGRATED, false)) {
+ Cling cling = initCling(R.id.folder_cling, R.id.cling_scrim,
+ true, true);
+ Folder openFolder = mLauncher.getWorkspace().getOpenFolder();
+ if (openFolder != null) {
+ Rect openFolderRect = new Rect();
+ openFolder.getHitRect(openFolderRect);
+ cling.setOpenFolderRect(openFolderRect);
+ openFolder.bringToFront();
+ }
+ return cling;
+ } else {
+ removeCling(R.id.folder_cling);
+ return null;
+ }
+ }
+
+ public static void synchonouslyMarkFirstRunClingDismissed(Context ctx) {
+ SharedPreferences prefs = ctx.getSharedPreferences(
+ LauncherAppState.getSharedPreferencesKey(),Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putBoolean(LauncherClings.FIRST_RUN_CLING_DISMISSED_KEY, true);
+ editor.commit();
+ }
+
+ public void markFolderClingDismissed() {
+ SharedPreferences.Editor editor = mLauncher.getSharedPrefs().edit();
+ editor.putBoolean(LauncherClings.FOLDER_CLING_DISMISSED_KEY, true);
+ editor.apply();
+ }
+
+ /** Removes the cling outright from the DragLayer */
+ private void removeCling(int id) {
+ final View cling = mLauncher.findViewById(id);
+ if (cling != null) {
+ final ViewGroup parent = (ViewGroup) cling.getParent();
+ parent.post(new Runnable() {
+ @Override
+ public void run() {
+ parent.removeView(cling);
+ }
+ });
+ mHideFromAccessibilityHelper.restoreImportantForAccessibility(mLauncher.getDragLayer());
+ }
+ }
+
+ /** Hides the specified Cling */
+ private void dismissCling(final Cling cling, final Runnable postAnimationCb,
+ final String flag, int duration, boolean restoreNavBarVisibilty) {
+ // To catch cases where siblings of top-level views are made invisible, just check whether
+ // the cling is directly set to GONE before dismissing it.
+ if (cling != null && cling.getVisibility() != View.GONE) {
+ final Runnable cleanUpClingCb = new Runnable() {
+ public void run() {
+ cling.cleanup();
+ SharedPreferences.Editor editor = mLauncher.getSharedPrefs().edit();
+ editor.putBoolean(flag, true);
+ editor.apply();
+ if (postAnimationCb != null) {
+ postAnimationCb.run();
+ }
+ }
+ };
+ if (duration <= 0) {
+ cleanUpClingCb.run();
+ } else {
+ cling.hide(duration, cleanUpClingCb);
+ }
+ mHideFromAccessibilityHelper.restoreImportantForAccessibility(mLauncher.getDragLayer());
+
+ if (restoreNavBarVisibilty) {
+ cling.setSystemUiVisibility(cling.getSystemUiVisibility() &
+ ~View.SYSTEM_UI_FLAG_LOW_PROFILE);
+ }
+ }
+ }
+
+ public void dismissFirstRunCling(View v) {
+ Cling cling = (Cling) mLauncher.findViewById(R.id.first_run_cling);
+ Runnable cb = new Runnable() {
+ public void run() {
+ // Show the workspace cling next
+ showWorkspaceCling();
+ }
+ };
+ dismissCling(cling, cb, FIRST_RUN_CLING_DISMISSED_KEY,
+ DISMISS_CLING_DURATION, false);
+
+ // Fade out the search bar for the workspace cling coming up
+ mLauncher.getSearchBar().hideSearchBar(true);
+ }
+
+ private void dismissMigrationCling() {
+ mLauncher.showWorkspaceSearchAndHotseat();
+ Runnable dismissCb = new Runnable() {
+ public void run() {
+ Cling cling = (Cling) mLauncher.findViewById(R.id.migration_cling);
+ Runnable cb = new Runnable() {
+ public void run() {
+ // Show the migration workspace cling next
+ showMigrationWorkspaceCling();
+ }
+ };
+ dismissCling(cling, cb, MIGRATION_CLING_DISMISSED_KEY,
+ DISMISS_CLING_DURATION, true);
+ }
+ };
+ mLauncher.getWorkspace().post(dismissCb);
+ }
+
+ private void dismissAnyWorkspaceCling(Cling cling, String key, View v) {
+ Runnable cb = null;
+ if (v == null) {
+ cb = new Runnable() {
+ public void run() {
+ mLauncher.getWorkspace().enterOverviewMode();
+ }
+ };
+ }
+ dismissCling(cling, cb, key, DISMISS_CLING_DURATION, true);
+
+ // Fade in the search bar
+ mLauncher.getSearchBar().showSearchBar(true);
+ }
+
+ public void dismissMigrationClingCopyApps(View v) {
+ // Copy the shortcuts from the old database
+ LauncherModel model = mLauncher.getModel();
+ model.resetLoadedState(false, true);
+ model.startLoader(false, PagedView.INVALID_RESTORE_PAGE,
+ LauncherModel.LOADER_FLAG_CLEAR_WORKSPACE
+ | LauncherModel.LOADER_FLAG_MIGRATE_SHORTCUTS);
+
+ // Set the flag to skip the folder cling
+ String spKey = LauncherAppState.getSharedPreferencesKey();
+ SharedPreferences sp = mLauncher.getSharedPreferences(spKey, Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = sp.edit();
+ editor.putBoolean(Launcher.USER_HAS_MIGRATED, true);
+ editor.apply();
+
+ // Disable the migration cling
+ dismissMigrationCling();
+ }
+
+ public void dismissMigrationClingUseDefault(View v) {
+ // Clear the workspace
+ LauncherModel model = mLauncher.getModel();
+ model.resetLoadedState(false, true);
+ model.startLoader(false, PagedView.INVALID_RESTORE_PAGE,
+ LauncherModel.LOADER_FLAG_CLEAR_WORKSPACE);
+
+ // Disable the migration cling
+ dismissMigrationCling();
+ }
+
+ public void dismissMigrationWorkspaceCling(View v) {
+ Cling cling = (Cling) mLauncher.findViewById(R.id.migration_workspace_cling);
+ dismissAnyWorkspaceCling(cling, MIGRATION_WORKSPACE_CLING_DISMISSED_KEY, v);
+ }
+
+ public void dismissWorkspaceCling(View v) {
+ Cling cling = (Cling) mLauncher.findViewById(R.id.workspace_cling);
+ dismissAnyWorkspaceCling(cling, WORKSPACE_CLING_DISMISSED_KEY, v);
+ }
+
+ public void dismissFolderCling(View v) {
+ Cling cling = (Cling) mLauncher.findViewById(R.id.folder_cling);
+ dismissCling(cling, null, FOLDER_CLING_DISMISSED_KEY,
+ DISMISS_CLING_DURATION, true);
+ }
+}
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index e06fe5fb3..9fe0bf3a5 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -48,6 +48,8 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.provider.BaseColumns;
+import android.text.TextUtils;
+import android.provider.Settings;
import android.util.Log;
import android.util.Pair;
@@ -78,13 +80,21 @@ import java.util.concurrent.atomic.AtomicBoolean;
public class LauncherModel extends BroadcastReceiver {
static final boolean DEBUG_LOADERS = false;
static final String TAG = "Launcher.Model";
+ public static final String SETTINGS_PROTECTED_COMPONENTS = "protected_components";
// true = use a "More Apps" folder for non-workspace apps on upgrade
// false = strew non-workspace apps across the workspace on upgrade
public static final boolean UPGRADE_USE_MORE_APPS_FOLDER = false;
+ public static final int LOADER_FLAG_NONE = 0;
+ public static final int LOADER_FLAG_CLEAR_WORKSPACE = 1 << 0;
+ public static final int LOADER_FLAG_MIGRATE_SHORTCUTS = 1 << 1;
+
private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons
+ private static final long INVALID_SCREEN_ID = -1L;
+
private final boolean mAppsCanBeOnRemoveableStorage;
+ private final boolean mOldContentProviderExists;
private final LauncherAppState mApp;
private final Object mLock = new Object();
@@ -175,8 +185,7 @@ public class LauncherModel extends BroadcastReceiver {
ArrayList<AppInfo> addedApps);
public void bindAppsUpdated(ArrayList<AppInfo> apps);
public void bindComponentsRemoved(ArrayList<String> packageNames,
- ArrayList<AppInfo> appInfos,
- boolean matchPackageNamesOnly);
+ ArrayList<AppInfo> appInfos);
public void bindPackagesUpdated(ArrayList<Object> widgetsAndShortcuts);
public void bindSearchablesChanged();
public void onPageBoundSynchronously(int page);
@@ -188,16 +197,16 @@ public class LauncherModel extends BroadcastReceiver {
}
LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
- final Context context = app.getContext();
+ Context context = app.getContext();
+ ContentResolver contentResolver = context.getContentResolver();
mAppsCanBeOnRemoveableStorage = Environment.isExternalStorageRemovable();
+ mOldContentProviderExists = (contentResolver.acquireContentProviderClient(
+ LauncherSettings.Favorites.OLD_CONTENT_URI) != null);
mApp = app;
mBgAllAppsList = new AllAppsList(iconCache, appFilter);
mIconCache = iconCache;
- mDefaultIcon = Utilities.createIconBitmap(
- mIconCache.getFullResDefaultActivityIcon(), context);
-
final Resources res = context.getResources();
Configuration config = res.getConfiguration();
mPreviousConfigMcc = config.mcc;
@@ -228,6 +237,10 @@ public class LauncherModel extends BroadcastReceiver {
}
}
+ boolean canMigrateFromOldLauncherDb(Launcher launcher) {
+ return mOldContentProviderExists && !launcher.isLauncherPreinstalled() ;
+ }
+
static boolean findNextAvailableIconSpaceInScreen(ArrayList<ItemInfo> items, int[] xy,
long screen) {
LauncherAppState app = LauncherAppState.getInstance();
@@ -289,14 +302,58 @@ public class LauncherModel extends BroadcastReceiver {
return null;
}
- public void addAndBindAddedApps(final Context context, final ArrayList<ItemInfo> workspaceApps,
- final ArrayList<AppInfo> allAppsApps) {
- Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
- addAndBindAddedApps(context, workspaceApps, cb, allAppsApps);
+ public void addAppsToAllApps(final Context ctx, final ArrayList<AppInfo> allAppsApps) {
+ final Callbacks callbacks = mCallbacks != null ? mCallbacks.get() : null;
+
+ if (allAppsApps == null) {
+ throw new RuntimeException("allAppsApps must not be null");
+ }
+ if (allAppsApps.isEmpty()) {
+ return;
+ }
+
+ final ArrayList<AppInfo> restoredAppsFinal = new ArrayList<AppInfo>();
+ Iterator<AppInfo> iter = allAppsApps.iterator();
+ while (iter.hasNext()) {
+ ItemInfo a = iter.next();
+ if (LauncherModel.appWasRestored(ctx, a.getIntent())) {
+ restoredAppsFinal.add((AppInfo) a);
+ }
+ }
+
+ // Process the newly added applications and add them to the database first
+ Runnable r = new Runnable() {
+ public void run() {
+ runOnMainThread(new Runnable() {
+ public void run() {
+ Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
+ if (callbacks == cb && cb != null) {
+ if (!restoredAppsFinal.isEmpty()) {
+ for (AppInfo info : restoredAppsFinal) {
+ final Intent intent = info.getIntent();
+ if (intent != null) {
+ mIconCache.deletePreloadedIcon(intent.getComponent());
+ }
+ }
+ callbacks.bindAppsUpdated(restoredAppsFinal);
+ }
+ callbacks.bindAppsAdded(null, null, null, allAppsApps);
+ }
+ }
+ });
+ }
+ };
+ runOnWorkerThread(r);
}
- public void addAndBindAddedApps(final Context context, final ArrayList<ItemInfo> workspaceApps,
- final Callbacks callbacks, final ArrayList<AppInfo> allAppsApps) {
- if (workspaceApps.isEmpty() && allAppsApps.isEmpty()) {
+
+ public void addAndBindAddedWorkspaceApps(final Context context,
+ final ArrayList<ItemInfo> workspaceApps) {
+ final Callbacks callbacks = mCallbacks != null ? mCallbacks.get() : null;
+
+ if (workspaceApps == null) {
+ throw new RuntimeException("workspaceApps and allAppsApps must not be null");
+ }
+ if (workspaceApps.isEmpty()) {
return;
}
// Process the newly added applications and add them to the database first
@@ -304,6 +361,7 @@ public class LauncherModel extends BroadcastReceiver {
public void run() {
final ArrayList<ItemInfo> addedShortcutsFinal = new ArrayList<ItemInfo>();
final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<Long>();
+ final ArrayList<AppInfo> restoredAppsFinal = new ArrayList<AppInfo>();
// Get the list of workspace screens. We need to append to this list and
// can not use sBgWorkspaceScreens because loadWorkspace() may not have been
@@ -324,6 +382,11 @@ public class LauncherModel extends BroadcastReceiver {
// Short-circuit this logic if the icon exists somewhere on the workspace
if (LauncherModel.shortcutExists(context, name, launchIntent)) {
+ // Only InstallShortcutReceiver sends us shortcutInfos, ignore them
+ if (a instanceof AppInfo &&
+ LauncherModel.appWasRestored(context, launchIntent)) {
+ restoredAppsFinal.add((AppInfo) a);
+ }
continue;
}
@@ -379,7 +442,7 @@ public class LauncherModel extends BroadcastReceiver {
// Update the workspace screens
updateWorkspaceScreenOrder(context, workspaceScreens);
- if (!addedShortcutsFinal.isEmpty() || !allAppsApps.isEmpty()) {
+ if (!addedShortcutsFinal.isEmpty()) {
runOnMainThread(new Runnable() {
public void run() {
Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
@@ -398,7 +461,10 @@ public class LauncherModel extends BroadcastReceiver {
}
}
callbacks.bindAppsAdded(addedWorkspaceScreensFinal,
- addNotAnimated, addAnimated, allAppsApps);
+ addNotAnimated, addAnimated, null);
+ if (!restoredAppsFinal.isEmpty()) {
+ callbacks.bindAppsUpdated(restoredAppsFinal);
+ }
}
}
});
@@ -409,6 +475,11 @@ public class LauncherModel extends BroadcastReceiver {
}
public Bitmap getFallbackIcon() {
+ if (mDefaultIcon == null) {
+ final Context context = LauncherAppState.getInstance().getContext();
+ mDefaultIcon = Utilities.createIconBitmap(
+ mIconCache.getFullResDefaultActivityIcon(), context);
+ }
return Bitmap.createBitmap(mDefaultIcon);
}
@@ -504,8 +575,7 @@ public class LauncherModel extends BroadcastReceiver {
if (stackTrace != null) {
e.setStackTrace(stackTrace);
}
- // TODO: something breaks this in the upgrade path
- //throw e;
+ throw e;
}
}
@@ -589,8 +659,9 @@ public class LauncherModel extends BroadcastReceiver {
// as in Workspace.onDrop. Here, we just add/remove them from the list of items
// that are on the desktop, as appropriate
ItemInfo modelItem = sBgItemsIdMap.get(itemId);
- if (modelItem.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
- modelItem.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+ if (modelItem != null &&
+ (modelItem.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
+ modelItem.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT)) {
switch (modelItem.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
@@ -760,6 +831,30 @@ public class LauncherModel extends BroadcastReceiver {
}
/**
+ * Returns true if the shortcuts already exists in the database.
+ * we identify a shortcut by the component name of the intent.
+ */
+ static boolean appWasRestored(Context context, Intent intent) {
+ final ContentResolver cr = context.getContentResolver();
+ final ComponentName component = intent.getComponent();
+ if (component == null) {
+ return false;
+ }
+ String componentName = component.flattenToString();
+ final String where = "intent glob \"*component=" + componentName + "*\" and restored = 1";
+ Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI,
+ new String[]{"intent", "restored"}, where, null, null);
+ boolean result = false;
+ try {
+ result = c.moveToFirst();
+ } finally {
+ c.close();
+ }
+ Log.d(TAG, "shortcutWasRestored is " + result + " for " + componentName);
+ return result;
+ }
+
+ /**
* Returns an ItemInfo array containing all the items in the LauncherModel.
* The ItemInfo.id is not set through this function.
*/
@@ -819,6 +914,7 @@ public class LauncherModel extends BroadcastReceiver {
final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
+ final int hiddenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.HIDDEN);
FolderInfo folderInfo = null;
switch (c.getInt(itemTypeIndex)) {
@@ -833,6 +929,7 @@ public class LauncherModel extends BroadcastReceiver {
folderInfo.screenId = c.getInt(screenIndex);
folderInfo.cellX = c.getInt(cellXIndex);
folderInfo.cellY = c.getInt(cellYIndex);
+ folderInfo.hidden = c.getInt(hiddenIndex) > 0;
return folderInfo;
}
@@ -1017,6 +1114,10 @@ public class LauncherModel extends BroadcastReceiver {
* a list of screen ids in the order that they should appear.
*/
void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) {
+ // Log to disk
+ Launcher.addDumpLog(TAG, "11683562 - updateWorkspaceScreenOrder()", true);
+ Launcher.addDumpLog(TAG, "11683562 - screens: " + TextUtils.join(", ", screens), true);
+
final ArrayList<Long> screensCopy = new ArrayList<Long>(screens);
final ContentResolver cr = context.getContentResolver();
final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
@@ -1033,18 +1134,23 @@ public class LauncherModel extends BroadcastReceiver {
Runnable r = new Runnable() {
@Override
public void run() {
+ ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
// Clear the table
- cr.delete(uri, null, null);
+ ops.add(ContentProviderOperation.newDelete(uri).build());
int count = screensCopy.size();
- ContentValues[] values = new ContentValues[count];
for (int i = 0; i < count; i++) {
ContentValues v = new ContentValues();
long screenId = screensCopy.get(i);
v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
- values[i] = v;
+ ops.add(ContentProviderOperation.newInsert(uri).withValues(v).build());
+ }
+
+ try {
+ cr.applyBatch(LauncherProvider.AUTHORITY, ops);
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
}
- cr.bulkInsert(uri, values);
synchronized (sBgLock) {
sBgWorkspaceScreens.clear();
@@ -1139,15 +1245,29 @@ public class LauncherModel extends BroadcastReceiver {
}
} else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
- // First, schedule to add these apps back in.
+ final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
- enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_ADD, packages));
- // Then, rebind everything.
- startLoaderFromBackground();
+ if (!replacing) {
+ enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_ADD, packages));
+ if (mAppsCanBeOnRemoveableStorage) {
+ // Only rebind if we support removable storage. It catches the case where
+ // apps on the external sd card need to be reloaded
+ startLoaderFromBackground();
+ }
+ } else {
+ // If we are replacing then just update the packages in the list
+ enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE,
+ packages));
+ }
} else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
- String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
- enqueuePackageUpdated(new PackageUpdatedTask(
- PackageUpdatedTask.OP_UNAVAILABLE, packages));
+ final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
+ if (!replacing) {
+ String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ enqueuePackageUpdated(new PackageUpdatedTask(
+ PackageUpdatedTask.OP_UNAVAILABLE, packages));
+ }
+ // else, we are replacing the packages, so ignore this event and wait for
+ // EXTERNAL_APPLICATIONS_AVAILABLE to update the packages at that time
} else if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
// If we have changed locale we need to clear out the labels in all apps/workspace.
forceReload();
@@ -1211,7 +1331,7 @@ public class LauncherModel extends BroadcastReceiver {
}
}
if (runLoader) {
- startLoader(false, -1);
+ startLoader(false, PagedView.INVALID_RESTORE_PAGE);
}
}
@@ -1230,6 +1350,10 @@ public class LauncherModel extends BroadcastReceiver {
}
public void startLoader(boolean isLaunching, int synchronousBindPage) {
+ startLoader(isLaunching, synchronousBindPage, LOADER_FLAG_NONE);
+ }
+
+ public void startLoader(boolean isLaunching, int synchronousBindPage, int loadFlags) {
synchronized (mLock) {
if (DEBUG_LOADERS) {
Log.d(TAG, "startLoader isLaunching=" + isLaunching);
@@ -1244,8 +1368,9 @@ public class LauncherModel extends BroadcastReceiver {
// If there is already one running, tell it to stop.
// also, don't downgrade isLaunching if we're already running
isLaunching = isLaunching || stopLoaderLocked();
- mLoaderTask = new LoaderTask(mApp.getContext(), isLaunching);
- if (synchronousBindPage > -1 && mAllAppsLoaded && mWorkspaceLoaded) {
+ mLoaderTask = new LoaderTask(mApp.getContext(), isLaunching, loadFlags);
+ if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE
+ && mAllAppsLoaded && mWorkspaceLoaded) {
mLoaderTask.runBindSynchronousPage(synchronousBindPage);
} else {
sWorkerThread.setPriority(Thread.NORM_PRIORITY);
@@ -1297,6 +1422,15 @@ public class LauncherModel extends BroadcastReceiver {
} finally {
sc.close();
}
+
+ // Log to disk
+ Launcher.addDumpLog(TAG, "11683562 - loadWorkspaceScreensDb()", true);
+ ArrayList<String> orderedScreensPairs= new ArrayList<String>();
+ for (Integer i : orderedScreens.keySet()) {
+ orderedScreensPairs.add("{ " + i + ": " + orderedScreens.get(i) + " }");
+ }
+ Launcher.addDumpLog(TAG, "11683562 - screens: " +
+ TextUtils.join(", ", orderedScreensPairs), true);
return orderedScreens;
}
@@ -1313,75 +1447,6 @@ public class LauncherModel extends BroadcastReceiver {
return false;
}
- // check & update map of what's occupied; used to discard overlapping/invalid items
- public boolean checkItemPlacement(HashMap<Long, ItemInfo[][]> occupied, ItemInfo item,
- AtomicBoolean deleteOnItemOverlap) {
- LauncherAppState app = LauncherAppState.getInstance();
- DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
-
- long containerIndex = item.screenId;
- if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
- if (occupied.containsKey((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT)) {
- if (occupied.get((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT)
- [(int) item.screenId][0] != null) {
- Log.e(TAG, "Error loading shortcut into hotseat " + item
- + " into position (" + item.screenId + ":" + item.cellX + ","
- + item.cellY + ") occupied by "
- + occupied.get((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT)
- [(int) item.screenId][0]);
- if (occupied.get((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT)
- [(int) item.screenId][0].itemType == LauncherSettings.Favorites.ITEM_TYPE_ALLAPPS) {
- deleteOnItemOverlap.set(true);
- }
- return false;
- } else {
- ItemInfo[][] hotseatItems = occupied.get(
- (long) LauncherSettings.Favorites.CONTAINER_HOTSEAT);
- hotseatItems[(int) item.screenId][0] = item;
- return true;
- }
- } else {
- ItemInfo[][] items = new ItemInfo[(int) grid.numHotseatIcons][1];
- items[(int) item.screenId][0] = item;
- occupied.put((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT, items);
- return true;
- }
- } else if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP) {
- // Skip further checking if it is not the hotseat or workspace container
- return true;
- }
-
- int countX = (int) grid.numColumns;
- int countY = (int) grid.numRows;
-
- if (!occupied.containsKey(item.screenId)) {
- ItemInfo[][] items = new ItemInfo[countX + 1][countY + 1];
- occupied.put(item.screenId, items);
- }
-
- ItemInfo[][] screens = occupied.get(item.screenId);
- // Check if any workspace icons overlap with each other
- for (int x = item.cellX; x < (item.cellX+item.spanX); x++) {
- for (int y = item.cellY; y < (item.cellY+item.spanY); y++) {
- if (screens[x][y] != null) {
- Log.e(TAG, "Error loading shortcut " + item
- + " into cell (" + containerIndex + "-" + item.screenId + ":"
- + x + "," + y
- + ") occupied by "
- + screens[x][y]);
- return false;
- }
- }
- }
- for (int x = item.cellX; x < (item.cellX+item.spanX); x++) {
- for (int y = item.cellY; y < (item.cellY+item.spanY); y++) {
- screens[x][y] = item;
- }
- }
-
- return true;
- }
-
/**
* Runnable for the thread that loads the contents of the launcher:
* - workspace icons
@@ -1394,13 +1459,15 @@ public class LauncherModel extends BroadcastReceiver {
private boolean mIsLoadingAndBindingWorkspace;
private boolean mStopped;
private boolean mLoadAndBindStepFinished;
+ private int mFlags;
private HashMap<Object, CharSequence> mLabelCache;
- LoaderTask(Context context, boolean isLaunching) {
+ LoaderTask(Context context, boolean isLaunching, int flags) {
mContext = context;
mIsLaunching = isLaunching;
mLabelCache = new HashMap<Object, CharSequence>();
+ mFlags = flags;
}
boolean isLaunching() {
@@ -1473,7 +1540,7 @@ public class LauncherModel extends BroadcastReceiver {
}
void runBindSynchronousPage(int synchronousBindPage) {
- if (synchronousBindPage < 0) {
+ if (synchronousBindPage == PagedView.INVALID_RESTORE_PAGE) {
// Ensure that we have a valid page index to load synchronously
throw new RuntimeException("Should not call runBindSynchronousPage() without " +
"valid page index");
@@ -1562,7 +1629,7 @@ public class LauncherModel extends BroadcastReceiver {
sBgDbIconCache.clear();
}
- if (AppsCustomizePagedView.DISABLE_ALL_APPS) {
+ if (LauncherAppState.isDisableAllApps()) {
// Ensure that all the applications that are in the system are
// represented on the home screen.
if (!UPGRADE_USE_MORE_APPS_FOLDER || !isUpgrade) {
@@ -1638,16 +1705,90 @@ public class LauncherModel extends BroadcastReceiver {
}
}
if (!added.isEmpty()) {
- Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
- addAndBindAddedApps(context, added, cb, null);
+ addAndBindAddedWorkspaceApps(context, added);
}
}
- private boolean checkItemDimensions(ItemInfo info) {
+ private boolean checkItemPlacement(HashMap<Long, ItemInfo[][]> occupied, ItemInfo item,
+ AtomicBoolean deleteOnInvalidPlacement) {
LauncherAppState app = LauncherAppState.getInstance();
DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
- return (info.cellX + info.spanX) > (int) grid.numColumns ||
- (info.cellY + info.spanY) > (int) grid.numRows;
+ final int countX = (int) grid.numColumns;
+ final int countY = (int) grid.numRows;
+ long containerIndex = item.screenId;
+ if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+ // Return early if we detect that an item is under the hotseat button
+ if (mCallbacks == null) {
+ deleteOnInvalidPlacement.set(true);
+ Log.e(TAG, "Error loading shortcut into hotseat " + item
+ + " into position (" + item.screenId + ":" + item.cellX + ","
+ + item.cellY + ") occupied by all apps");
+ return false;
+ }
+ final ItemInfo[][] hotseatItems =
+ occupied.get((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT);
+ if (item.screenId >= grid.numHotseatIcons) {
+ Log.e(TAG, "Error loading shortcut " + item
+ + " into hotseat position " + item.screenId
+ + ", position out of bounds: (0 to " + (grid.numHotseatIcons - 1)
+ + ")");
+ return false;
+ }
+ if (hotseatItems != null) {
+ if (hotseatItems[(int) item.screenId][0] != null) {
+ Log.e(TAG, "Error loading shortcut into hotseat " + item
+ + " into position (" + item.screenId + ":" + item.cellX + ","
+ + item.cellY + ") occupied by "
+ + occupied.get(LauncherSettings.Favorites.CONTAINER_HOTSEAT)
+ [(int) item.screenId][0]);
+ return false;
+ } else {
+ hotseatItems[(int) item.screenId][0] = item;
+ return true;
+ }
+ } else {
+ final ItemInfo[][] items = new ItemInfo[(int) grid.numHotseatIcons][1];
+ items[(int) item.screenId][0] = item;
+ occupied.put((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT, items);
+ return true;
+ }
+ } else if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+ // Skip further checking if it is not the hotseat or workspace container
+ return true;
+ }
+ if (!occupied.containsKey(item.screenId)) {
+ ItemInfo[][] items = new ItemInfo[countX + 1][countY + 1];
+ occupied.put(item.screenId, items);
+ }
+ final ItemInfo[][] screens = occupied.get(item.screenId);
+ if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
+ item.cellX < 0 || item.cellY < 0 ||
+ item.cellX + item.spanX > countX || item.cellY + item.spanY > countY) {
+ Log.e(TAG, "Error loading shortcut " + item
+ + " into cell (" + containerIndex + "-" + item.screenId + ":"
+ + item.cellX + "," + item.cellY
+ + ") out of screen bounds ( " + countX + "x" + countY + ")");
+ return false;
+ }
+ // Check if any workspace icons overlap with each other
+ for (int x = item.cellX; x < (item.cellX+item.spanX); x++) {
+ for (int y = item.cellY; y < (item.cellY+item.spanY); y++) {
+ if (screens[x][y] != null) {
+ Log.e(TAG, "Error loading shortcut " + item
+ + " into cell (" + containerIndex + "-" + item.screenId + ":"
+ + x + "," + y
+ + ") occupied by "
+ + screens[x][y]);
+ return false;
+ }
+ }
+ }
+ for (int x = item.cellX; x < (item.cellX+item.spanX); x++) {
+ for (int y = item.cellY; y < (item.cellY+item.spanY); y++) {
+ screens[x][y] = item;
+ }
+ }
+ return true;
}
/** Clears all the sBg data structures */
@@ -1662,8 +1803,11 @@ public class LauncherModel extends BroadcastReceiver {
}
}
- /** Returns whether this is an upgradge path */
+ /** Returns whether this is an upgrade path */
private boolean loadWorkspace() {
+ // Log to disk
+ Launcher.addDumpLog(TAG, "11683562 - loadWorkspace()", true);
+
final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
final Context context = mContext;
@@ -1677,16 +1821,33 @@ public class LauncherModel extends BroadcastReceiver {
int countX = (int) grid.numColumns;
int countY = (int) grid.numRows;
- // Make sure the default workspace is loaded, if needed
- LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary(0);
+ if ((mFlags & LOADER_FLAG_CLEAR_WORKSPACE) != 0) {
+ Launcher.addDumpLog(TAG, "loadWorkspace: resetting launcher database", true);
+ LauncherAppState.getLauncherProvider().deleteDatabase();
+ }
+
+ if ((mFlags & LOADER_FLAG_MIGRATE_SHORTCUTS) != 0) {
+ // append the user's Launcher2 shortcuts
+ Launcher.addDumpLog(TAG, "loadWorkspace: migrating from launcher2", true);
+ LauncherAppState.getLauncherProvider().migrateLauncher2Shortcuts();
+ } else {
+ // Make sure the default workspace is loaded
+ Launcher.addDumpLog(TAG, "loadWorkspace: loading default favorites", false);
+ LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary(0);
+ }
// Check if we need to do any upgrade-path logic
+ // (Includes having just imported default favorites)
boolean loadedOldDb = LauncherAppState.getLauncherProvider().justLoadedOldDb();
+ // Log to disk
+ Launcher.addDumpLog(TAG, "11683562 - loadedOldDb: " + loadedOldDb, true);
+
synchronized (sBgLock) {
clearSBgDataStructures();
final ArrayList<Long> itemsToRemove = new ArrayList<Long>();
+ final ArrayList<Long> restoredRows = new ArrayList<Long>();
final Uri contentUri = LauncherSettings.Favorites.CONTENT_URI;
if (DEBUG_LOADERS) Log.d(TAG, "loading model from " + contentUri);
final Cursor c = contentResolver.query(contentUri, null, null, null, null);
@@ -1727,8 +1888,11 @@ public class LauncherModel extends BroadcastReceiver {
(LauncherSettings.Favorites.SPANX);
final int spanYIndex = c.getColumnIndexOrThrow(
LauncherSettings.Favorites.SPANY);
- //final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
- //final int displayModeIndex = c.getColumnIndexOrThrow(
+ final int restoredIndex = c.getColumnIndexOrThrow(
+ LauncherSettings.Favorites.RESTORED);
+ final int hiddenIndex = c.getColumnIndexOrThrow(
+ LauncherSettings.Favorites.HIDDEN);
+ //final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI); //final int displayModeIndex = c.getColumnIndexOrThrow(
// LauncherSettings.Favorites.DISPLAY_MODE);
ShortcutInfo info;
@@ -1739,9 +1903,10 @@ public class LauncherModel extends BroadcastReceiver {
Intent intent = null;
while (!mStopped && c.moveToNext()) {
- AtomicBoolean deleteOnItemOverlap = new AtomicBoolean(false);
+ AtomicBoolean deleteOnInvalidPlacement = new AtomicBoolean(false);
try {
int itemType = c.getInt(itemTypeIndex);
+ boolean restored = 0 != c.getInt(restoredIndex);
switch (itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
@@ -1754,25 +1919,45 @@ public class LauncherModel extends BroadcastReceiver {
intent = Intent.parseUri(intentDescription, 0);
ComponentName cn = intent.getComponent();
if (cn != null && !isValidPackageComponent(manager, cn)) {
- if (!mAppsCanBeOnRemoveableStorage) {
- // Log the invalid package, and remove it from the db
- Launcher.addDumpLog(TAG, "Invalid package removed: " + cn, true);
- itemsToRemove.add(id);
+ if (restored) {
+ // might be installed later
+ Launcher.addDumpLog(TAG,
+ "package not yet restored: " + cn, true);
} else {
- // If apps can be on external storage, then we just
- // leave them for the user to remove (maybe add
- // visual treatment to it)
- Launcher.addDumpLog(TAG, "Invalid package found: " + cn, true);
+ if (!mAppsCanBeOnRemoveableStorage) {
+ // Log the invalid package, and remove it
+ Launcher.addDumpLog(TAG,
+ "Invalid package removed: " + cn, true);
+ itemsToRemove.add(id);
+ } else {
+ // If apps can be on external storage, then we just
+ // leave them for the user to remove (maybe add
+ // visual treatment to it)
+ Launcher.addDumpLog(TAG,
+ "Invalid package found: " + cn, true);
+ }
+ continue;
}
- continue;
+ } else if (restored) {
+ // no special handling necessary for this restored item
+ restoredRows.add(id);
+ restored = false;
}
} catch (URISyntaxException e) {
- Launcher.addDumpLog(TAG, "Invalid uri: " + intentDescription, true);
+ Launcher.addDumpLog(TAG,
+ "Invalid uri: " + intentDescription, true);
continue;
}
}
- if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
+ if (restored) {
+ Launcher.addDumpLog(TAG,
+ "constructing info for partially restored package",
+ true);
+ info = getRestoredItemInfo(c, titleIndex, intent);
+ intent = getRestoredItemIntent(c, context, intent);
+ } else if (itemType ==
+ LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
info = getShortcutInfo(manager, intent, context, c, iconIndex,
titleIndex, mLabelCache);
} else if (itemType == LauncherSettings.Favorites.ITEM_TYPE_ALLAPPS) {
@@ -1806,18 +1991,11 @@ public class LauncherModel extends BroadcastReceiver {
info.cellY = c.getInt(cellYIndex);
info.spanX = 1;
info.spanY = 1;
- // Skip loading items that are out of bounds
- if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
- if (checkItemDimensions(info)) {
- Launcher.addDumpLog(TAG, "Skipped loading out of bounds shortcut: "
- + info + ", " + grid.numColumns + "x" + grid.numRows, true);
- continue;
- }
- }
+
// check & update map of what's occupied
- deleteOnItemOverlap.set(false);
- if (!checkItemPlacement(occupied, info, deleteOnItemOverlap)) {
- if (deleteOnItemOverlap.get()) {
+ deleteOnInvalidPlacement.set(false);
+ if (!checkItemPlacement(occupied, info, deleteOnInvalidPlacement)) {
+ if (deleteOnInvalidPlacement.get()) {
itemsToRemove.add(id);
}
break;
@@ -1858,19 +2036,13 @@ public class LauncherModel extends BroadcastReceiver {
folderInfo.cellY = c.getInt(cellYIndex);
folderInfo.spanX = 1;
folderInfo.spanY = 1;
+ folderInfo.hidden = c.getInt(hiddenIndex) > 0;
- // Skip loading items that are out of bounds
- if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
- if (checkItemDimensions(folderInfo)) {
- Log.d(TAG, "Skipped loading out of bounds folder");
- continue;
- }
- }
// check & update map of what's occupied
- deleteOnItemOverlap.set(false);
+ deleteOnInvalidPlacement.set(false);
if (!checkItemPlacement(occupied, folderInfo,
- deleteOnItemOverlap)) {
- if (deleteOnItemOverlap.get()) {
+ deleteOnInvalidPlacement)) {
+ if (deleteOnInvalidPlacement.get()) {
itemsToRemove.add(id);
}
break;
@@ -1883,6 +2055,11 @@ public class LauncherModel extends BroadcastReceiver {
break;
}
+ if (restored) {
+ // no special handling required for restored folders
+ restoredRows.add(id);
+ }
+
sBgItemsIdMap.put(folderInfo.id, folderInfo);
sBgFolders.put(folderInfo.id, folderInfo);
break;
@@ -1926,18 +2103,11 @@ public class LauncherModel extends BroadcastReceiver {
}
appWidgetInfo.container = c.getInt(containerIndex);
- // Skip loading items that are out of bounds
- if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
- if (checkItemDimensions(appWidgetInfo)) {
- Log.d(TAG, "Skipped loading out of bounds app widget");
- continue;
- }
- }
// check & update map of what's occupied
- deleteOnItemOverlap.set(false);
+ deleteOnInvalidPlacement.set(false);
if (!checkItemPlacement(occupied, appWidgetInfo,
- deleteOnItemOverlap)) {
- if (deleteOnItemOverlap.get()) {
+ deleteOnInvalidPlacement)) {
+ if (deleteOnInvalidPlacement.get()) {
itemsToRemove.add(id);
}
break;
@@ -1957,7 +2127,7 @@ public class LauncherModel extends BroadcastReceiver {
break;
}
} catch (Exception e) {
- Launcher.addDumpLog(TAG, "Desktop items loading interrupted: " + e, true);
+ Launcher.addDumpLog(TAG, "Desktop items loading interrupted", e, true);
}
}
} finally {
@@ -2003,6 +2173,25 @@ public class LauncherModel extends BroadcastReceiver {
}
}
+ if (restoredRows.size() > 0) {
+ ContentProviderClient updater = contentResolver.acquireContentProviderClient(
+ LauncherSettings.Favorites.CONTENT_URI);
+ // Update restored items that no longer require special handling
+ try {
+ StringBuilder selectionBuilder = new StringBuilder();
+ selectionBuilder.append(LauncherSettings.Favorites._ID);
+ selectionBuilder.append(" IN (");
+ selectionBuilder.append(TextUtils.join(", ", restoredRows));
+ selectionBuilder.append(")");
+ ContentValues values = new ContentValues();
+ values.put(LauncherSettings.Favorites.RESTORED, 0);
+ updater.update(LauncherSettings.Favorites.CONTENT_URI,
+ values, selectionBuilder.toString(), null);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Could not update restored rows");
+ }
+ }
+
if (loadedOldDb) {
long maxScreenId = 0;
// If we're importing we use the old screen order.
@@ -2017,6 +2206,10 @@ public class LauncherModel extends BroadcastReceiver {
}
}
Collections.sort(sBgWorkspaceScreens);
+ // Log to disk
+ Launcher.addDumpLog(TAG, "11683562 - maxScreenId: " + maxScreenId, true);
+ Launcher.addDumpLog(TAG, "11683562 - sBgWorkspaceScreens: " +
+ TextUtils.join(", ", sBgWorkspaceScreens), true);
LauncherAppState.getLauncherProvider().updateMaxScreenId(maxScreenId);
updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
@@ -2033,6 +2226,9 @@ public class LauncherModel extends BroadcastReceiver {
for (Integer i : orderedScreens.keySet()) {
sBgWorkspaceScreens.add(orderedScreens.get(i));
}
+ // Log to disk
+ Launcher.addDumpLog(TAG, "11683562 - sBgWorkspaceScreens: " +
+ TextUtils.join(", ", sBgWorkspaceScreens), true);
// Remove any empty screens
ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgWorkspaceScreens);
@@ -2046,6 +2242,10 @@ public class LauncherModel extends BroadcastReceiver {
// If there are any empty screens remove them, and update.
if (unusedScreens.size() != 0) {
+ // Log to disk
+ Launcher.addDumpLog(TAG, "11683562 - unusedScreens (to be removed): " +
+ TextUtils.join(", ", unusedScreens), true);
+
sBgWorkspaceScreens.removeAll(unusedScreens);
updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
}
@@ -2077,7 +2277,7 @@ public class LauncherModel extends BroadcastReceiver {
/** Filters the set of items who are directly or indirectly (via another container) on the
* specified screen. */
- private void filterCurrentWorkspaceItems(int currentScreen,
+ private void filterCurrentWorkspaceItems(long currentScreenId,
ArrayList<ItemInfo> allWorkspaceItems,
ArrayList<ItemInfo> currentScreenItems,
ArrayList<ItemInfo> otherScreenItems) {
@@ -2090,12 +2290,6 @@ public class LauncherModel extends BroadcastReceiver {
}
}
- // If we aren't filtering on a screen, then the set of items to load is the full set of
- // items given.
- if (currentScreen < 0) {
- currentScreenItems.addAll(allWorkspaceItems);
- }
-
// Order the set of items by their containers first, this allows use to walk through the
// list sequentially, build up a list of containers that are in the specified screen,
// as well as all items in those containers.
@@ -2108,7 +2302,7 @@ public class LauncherModel extends BroadcastReceiver {
});
for (ItemInfo info : allWorkspaceItems) {
if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
- if (info.screenId == currentScreen) {
+ if (info.screenId == currentScreenId) {
currentScreenItems.add(info);
itemsOnScreen.add(info.id);
} else {
@@ -2129,20 +2323,15 @@ public class LauncherModel extends BroadcastReceiver {
}
/** Filters the set of widgets which are on the specified screen. */
- private void filterCurrentAppWidgets(int currentScreen,
+ private void filterCurrentAppWidgets(long currentScreenId,
ArrayList<LauncherAppWidgetInfo> appWidgets,
ArrayList<LauncherAppWidgetInfo> currentScreenWidgets,
ArrayList<LauncherAppWidgetInfo> otherScreenWidgets) {
- // If we aren't filtering on a screen, then the set of items to load is the full set of
- // widgets given.
- if (currentScreen < 0) {
- currentScreenWidgets.addAll(appWidgets);
- }
for (LauncherAppWidgetInfo widget : appWidgets) {
if (widget == null) continue;
if (widget.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
- widget.screenId == currentScreen) {
+ widget.screenId == currentScreenId) {
currentScreenWidgets.add(widget);
} else {
otherScreenWidgets.add(widget);
@@ -2151,23 +2340,18 @@ public class LauncherModel extends BroadcastReceiver {
}
/** Filters the set of folders which are on the specified screen. */
- private void filterCurrentFolders(int currentScreen,
+ private void filterCurrentFolders(long currentScreenId,
HashMap<Long, ItemInfo> itemsIdMap,
HashMap<Long, FolderInfo> folders,
HashMap<Long, FolderInfo> currentScreenFolders,
HashMap<Long, FolderInfo> otherScreenFolders) {
- // If we aren't filtering on a screen, then the set of items to load is the full set of
- // widgets given.
- if (currentScreen < 0) {
- currentScreenFolders.putAll(folders);
- }
for (long id : folders.keySet()) {
ItemInfo info = itemsIdMap.get(id);
FolderInfo folder = folders.get(id);
if (info == null || folder == null) continue;
if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
- info.screenId == currentScreen) {
+ info.screenId == currentScreenId) {
currentScreenFolders.put(id, folder);
} else {
otherScreenFolders.put(id, folder);
@@ -2220,8 +2404,11 @@ public class LauncherModel extends BroadcastReceiver {
ArrayList<ComponentName> mHiddenApps = new ArrayList<ComponentName>();
ArrayList<String> mHiddenAppsPackages = new ArrayList<String>();
Context context = mApp.getContext();
- String[] flattened = SettingsProvider.getStringCustomDefault(context,
- SettingsProvider.SETTINGS_UI_DRAWER_HIDDEN_APPS, "").split("\\|");
+ // Since Trebuchet is compiled using the SDK we have to hardcode this string
+ String protectedComponents = Settings.Secure.getString(context.getContentResolver(),
+ SETTINGS_PROTECTED_COMPONENTS);
+ protectedComponents = protectedComponents == null ? "" : protectedComponents;
+ String[] flattened = protectedComponents.split("\\|");
boolean hideShortcuts = SettingsProvider.getBoolean(context,
SettingsProvider.SETTINGS_UI_DRAWER_REMOVE_HIDDEN_APPS_SHORTCUTS,
R.bool.preferences_interface_drawer_remove_hidden_apps_shortcuts_default);
@@ -2251,20 +2438,30 @@ public class LauncherModel extends BroadcastReceiver {
}
}
} else {
+ // Only remove items from folders that aren't hidden
final FolderInfo folder = (FolderInfo)item;
List<ShortcutInfo> shortcuts = folder.contents;
+
int NN = shortcuts.size() - 1;
for (int j = NN; j >= 0; j--) {
ShortcutInfo sci = shortcuts.get(j);
if (sci.intent != null && sci.intent.getComponent() != null) {
- if (mHiddenApps.contains(sci.intent.getComponent())) {
- LauncherModel.deleteItemFromDatabase(mContext, sci);
- folder.remove(sci);
+ if (!folder.hidden){
+ if (mHiddenApps.contains(sci.intent.getComponent())) {
+ LauncherModel.deleteItemFromDatabase(mContext, sci);
+ folder.remove(sci);
+ }
+ } else {
+ if (!mHiddenApps.contains(sci.intent.getComponent())) {
+ LauncherModel.deleteItemFromDatabase(mContext, sci);
+ folder.remove(sci);
+ }
}
+
}
}
- if (folder.contents.size() == 1 /*&& !(folder instanceof LiveFolderInfo)*/) {
+ if (folder.contents.size() == 1 && !folder.hidden) {
ShortcutInfo finalItem = folder.contents.get(0);
finalItem.container = folder.container;
LauncherModel.deleteItemFromDatabase(mContext, folder);
@@ -2382,13 +2579,7 @@ public class LauncherModel extends BroadcastReceiver {
return;
}
- final boolean isLoadingSynchronously = (synchronizeBindPage > -1);
- final int currentScreen = isLoadingSynchronously ? synchronizeBindPage :
- oldCallbacks.getCurrentWorkspaceScreen();
-
- // Load all the items that are on the current page first (and in the process, unbind
- // all the existing workspace items before we call startBinding() below.
- unbindWorkspaceItemsOnMainThread();
+ // Save a copy of all the bg-thread collections
ArrayList<ItemInfo> workspaceItems = new ArrayList<ItemInfo>();
ArrayList<LauncherAppWidgetInfo> appWidgets =
new ArrayList<LauncherAppWidgetInfo>();
@@ -2403,6 +2594,23 @@ public class LauncherModel extends BroadcastReceiver {
orderedScreenIds.addAll(sBgWorkspaceScreens);
}
+ final boolean isLoadingSynchronously =
+ synchronizeBindPage != PagedView.INVALID_RESTORE_PAGE;
+ int currScreen = isLoadingSynchronously ? synchronizeBindPage :
+ oldCallbacks.getCurrentWorkspaceScreen();
+ if (currScreen >= orderedScreenIds.size()) {
+ // There may be no workspace screens (just hotseat items and an empty page).
+ currScreen = PagedView.INVALID_RESTORE_PAGE;
+ }
+ final int currentScreen = currScreen;
+ final long currentScreenId = currentScreen < 0
+ ? INVALID_SCREEN_ID : orderedScreenIds.get(currentScreen);
+
+ // Load all the items that are on the current page first (and in the process, unbind
+ // all the existing workspace items before we call startBinding() below.
+ unbindWorkspaceItemsOnMainThread();
+
+ // Separate the items that are on the current screen, and all the other remaining items
ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<ItemInfo>();
ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<ItemInfo>();
ArrayList<LauncherAppWidgetInfo> currentAppWidgets =
@@ -2412,12 +2620,11 @@ public class LauncherModel extends BroadcastReceiver {
HashMap<Long, FolderInfo> currentFolders = new HashMap<Long, FolderInfo>();
HashMap<Long, FolderInfo> otherFolders = new HashMap<Long, FolderInfo>();
- // Separate the items that are on the current screen, and all the other remaining items
- filterCurrentWorkspaceItems(currentScreen, workspaceItems, currentWorkspaceItems,
+ filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems,
otherWorkspaceItems);
- filterCurrentAppWidgets(currentScreen, appWidgets, currentAppWidgets,
+ filterCurrentAppWidgets(currentScreenId, appWidgets, currentAppWidgets,
otherAppWidgets);
- filterCurrentFolders(currentScreen, itemsIdMap, folders, currentFolders,
+ filterCurrentFolders(currentScreenId, itemsIdMap, folders, currentFolders,
otherFolders);
sortWorkspaceItemsSpatially(currentWorkspaceItems);
sortWorkspaceItemsSpatially(otherWorkspaceItems);
@@ -2442,7 +2649,7 @@ public class LauncherModel extends BroadcastReceiver {
r = new Runnable() {
public void run() {
Callbacks callbacks = tryGetCallbacks(oldCallbacks);
- if (callbacks != null) {
+ if (callbacks != null && currentScreen != PagedView.INVALID_RESTORE_PAGE) {
callbacks.onPageBoundSynchronously(currentScreen);
}
}
@@ -2643,6 +2850,7 @@ public class LauncherModel extends BroadcastReceiver {
case OP_ADD:
for (int i=0; i<N; i++) {
if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]);
+ mIconCache.remove(packages[i]);
mBgAllAppsList.addPackage(context, packages[i]);
}
break;
@@ -2690,14 +2898,14 @@ public class LauncherModel extends BroadcastReceiver {
if (added != null) {
// Ensure that we add all the workspace applications to the db
- Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
- if (!AppsCustomizePagedView.DISABLE_ALL_APPS) {
- addAndBindAddedApps(context, new ArrayList<ItemInfo>(), cb, added);
- } else {
+ if (LauncherAppState.isDisableAllApps()) {
final ArrayList<ItemInfo> addedInfos = new ArrayList<ItemInfo>(added);
- addAndBindAddedApps(context, addedInfos, cb, added);
+ addAndBindAddedWorkspaceApps(context, addedInfos);
+ } else {
+ addAppsToAllApps(context, added);
}
}
+
if (modified != null) {
final ArrayList<AppInfo> modifiedFinal = modified;
@@ -2723,43 +2931,47 @@ public class LauncherModel extends BroadcastReceiver {
}
});
}
- // If a package has been removed, or an app has been removed as a result of
- // an update (for example), make the removed callback.
- if (mOp == OP_REMOVE || !removedApps.isEmpty()) {
- final boolean packageRemoved = (mOp == OP_REMOVE);
- final ArrayList<String> removedPackageNames =
- new ArrayList<String>(Arrays.asList(packages));
-
- // Update the launcher db to reflect the removal of apps
- if (packageRemoved) {
- for (String pn : removedPackageNames) {
- ArrayList<ItemInfo> infos = getItemInfoForPackageName(pn);
- for (ItemInfo i : infos) {
- deleteItemFromDatabase(context, i);
- }
- }
- // Remove any queued items from the install queue
- String spKey = LauncherAppState.getSharedPreferencesKey();
- SharedPreferences sp =
- context.getSharedPreferences(spKey, Context.MODE_PRIVATE);
- InstallShortcutReceiver.removeFromInstallQueue(sp, removedPackageNames);
- } else {
- for (AppInfo a : removedApps) {
- ArrayList<ItemInfo> infos =
- getItemInfoForComponentName(a.componentName);
- for (ItemInfo i : infos) {
- deleteItemFromDatabase(context, i);
- }
+ final ArrayList<String> removedPackageNames =
+ new ArrayList<String>();
+ if (mOp == OP_REMOVE) {
+ // Mark all packages in the broadcast to be removed
+ removedPackageNames.addAll(Arrays.asList(packages));
+ } else if (mOp == OP_UPDATE) {
+ // Mark disabled packages in the broadcast to be removed
+ final PackageManager pm = context.getPackageManager();
+ for (int i=0; i<N; i++) {
+ if (isPackageDisabled(pm, packages[i])) {
+ removedPackageNames.add(packages[i]);
}
}
-
+ }
+ // Remove all the components associated with this package
+ for (String pn : removedPackageNames) {
+ ArrayList<ItemInfo> infos = getItemInfoForPackageName(pn);
+ for (ItemInfo i : infos) {
+ deleteItemFromDatabase(context, i);
+ }
+ }
+ // Remove all the specific components
+ for (AppInfo a : removedApps) {
+ ArrayList<ItemInfo> infos = getItemInfoForComponentName(a.componentName);
+ for (ItemInfo i : infos) {
+ deleteItemFromDatabase(context, i);
+ }
+ }
+ if (!removedPackageNames.isEmpty() || !removedApps.isEmpty()) {
+ // Remove any queued items from the install queue
+ String spKey = LauncherAppState.getSharedPreferencesKey();
+ SharedPreferences sp =
+ context.getSharedPreferences(spKey, Context.MODE_PRIVATE);
+ InstallShortcutReceiver.removeFromInstallQueue(sp, removedPackageNames);
+ // Call the components-removed callback
mHandler.post(new Runnable() {
public void run() {
Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
if (callbacks == cb && cb != null) {
- callbacks.bindComponentsRemoved(removedPackageNames,
- removedApps, packageRemoved);
+ callbacks.bindComponentsRemoved(removedPackageNames, removedApps);
}
}
});
@@ -2801,19 +3013,27 @@ public class LauncherModel extends BroadcastReceiver {
return widgetsAndShortcuts;
}
- private boolean isValidPackageComponent(PackageManager pm, ComponentName cn) {
+ private static boolean isPackageDisabled(PackageManager pm, String packageName) {
+ try {
+ PackageInfo pi = pm.getPackageInfo(packageName, 0);
+ return !pi.applicationInfo.enabled;
+ } catch (NameNotFoundException e) {
+ // Fall through
+ }
+ return false;
+ }
+
+ public static boolean isValidPackageComponent(PackageManager pm, ComponentName cn) {
if (cn == null) {
return false;
}
+ if (isPackageDisabled(pm, cn.getPackageName())) {
+ return false;
+ }
try {
- // Skip if the application is disabled
- PackageInfo pi = pm.getPackageInfo(cn.getPackageName(), 0);
- if (!pi.applicationInfo.enabled) {
- return false;
- }
-
// Check the activity
+ PackageInfo pi = pm.getPackageInfo(cn.getPackageName(), 0);
return (pm.getActivityInfo(cn, 0) != null);
} catch (NameNotFoundException e) {
return false;
@@ -2821,6 +3041,41 @@ public class LauncherModel extends BroadcastReceiver {
}
/**
+ * Make an ShortcutInfo object for a restored application or shortcut item that points
+ * to a package that is not yet installed on the system.
+ */
+ public ShortcutInfo getRestoredItemInfo(Cursor cursor, int titleIndex, Intent intent) {
+ final ShortcutInfo info = new ShortcutInfo();
+ if (cursor != null) {
+ info.title = cursor.getString(titleIndex);
+ } else {
+ info.title = "";
+ }
+ info.setIcon(mIconCache.getIcon(intent, info.title.toString()));
+ info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
+ info.restoredIntent = intent;
+ return info;
+ }
+
+ /**
+ * Make an Intent object for a restored application or shortcut item that points
+ * to the market page for the item.
+ */
+ private Intent getRestoredItemIntent(Cursor c, Context context, Intent intent) {
+ final boolean debug = false;
+ ComponentName componentName = intent.getComponent();
+ Intent marketIntent = new Intent(Intent.ACTION_VIEW);
+ Uri marketUri = new Uri.Builder()
+ .scheme("market")
+ .authority("details")
+ .appendQueryParameter("id", componentName.getPackageName())
+ .build();
+ if (debug) Log.d(TAG, "manufactured intent uri: " + marketUri.toString());
+ marketIntent.setData(marketUri);
+ return marketIntent;
+ }
+
+ /**
* This is called from the code that adds shortcuts from the intent receiver. This
* doesn't have a Cursor, but
*/
@@ -2983,6 +3238,10 @@ public class LauncherModel extends BroadcastReceiver {
Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) {
return true;
}
+ // placeholder shortcuts get special treatment, let them through too.
+ if (info.getRestoredIntent() != null) {
+ return true;
+ }
}
return false;
}
diff --git a/src/com/android/launcher3/LauncherPreferencesBackupHelper.java b/src/com/android/launcher3/LauncherPreferencesBackupHelper.java
new file mode 100644
index 000000000..6f9c05c88
--- /dev/null
+++ b/src/com/android/launcher3/LauncherPreferencesBackupHelper.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.app.backup.BackupDataInputStream;
+import android.app.backup.SharedPreferencesBackupHelper;
+import android.content.Context;
+import android.util.Log;
+
+public class LauncherPreferencesBackupHelper extends SharedPreferencesBackupHelper {
+
+ private static final String TAG = "LauncherPreferencesBackupHelper";
+ private static final boolean VERBOSE = LauncherBackupAgentHelper.VERBOSE;
+
+ private final boolean mRestoreEnabled;
+
+ public LauncherPreferencesBackupHelper(Context context, String sharedPreferencesKey,
+ boolean restoreEnabled) {
+ super(context, sharedPreferencesKey);
+ mRestoreEnabled = restoreEnabled;
+ }
+
+ @Override
+ public void restoreEntity(BackupDataInputStream data) {
+ if (mRestoreEnabled) {
+ if (VERBOSE) Log.v(TAG, "restoring preferences");
+ super.restoreEntity(data);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 359a40323..9a2cb5070 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -22,13 +22,17 @@ import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.ContentProvider;
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
+import android.content.OperationApplicationException;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -48,6 +52,7 @@ import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
+import android.util.SparseArray;
import android.util.Xml;
import com.android.launcher3.LauncherSettings.Favorites;
@@ -56,12 +61,12 @@ import com.android.launcher3.config.ProviderConfig;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.ArrayList;
-import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
public class LauncherProvider extends ContentProvider {
private static final String TAG = "Launcher.LauncherProvider";
@@ -69,11 +74,14 @@ public class LauncherProvider extends ContentProvider {
private static final String DATABASE_NAME = "launcher.db";
- private static final int DATABASE_VERSION = 17;
+ private static final int DATABASE_VERSION = 20;
static final String OLD_AUTHORITY = "com.android.launcher2.settings";
static final String AUTHORITY = ProviderConfig.AUTHORITY;
+ // Should we attempt to load anything from the com.android.launcher2 provider?
+ static final boolean IMPORT_LAUNCHER2_DATABASE = false;
+
static final String TABLE_FAVORITES = "favorites";
static final String TABLE_WORKSPACE_SCREENS = "workspaceScreens";
static final String PARAMETER_NOTIFY = "notify";
@@ -106,6 +114,10 @@ public class LauncherProvider extends ContentProvider {
return true;
}
+ public boolean wasNewDbCreated() {
+ return mOpenHelper.wasNewDbCreated();
+ }
+
@Override
public String getType(Uri uri) {
SqlArguments args = new SqlArguments(uri, null, null);
@@ -133,9 +145,13 @@ public class LauncherProvider extends ContentProvider {
private static long dbInsertAndCheck(DatabaseHelper helper,
SQLiteDatabase db, String table, String nullColumnHack, ContentValues values) {
- if (!values.containsKey(LauncherSettings.Favorites._ID)) {
+ if (values == null) {
+ throw new RuntimeException("Error: attempting to insert null values");
+ }
+ if (!values.containsKey(LauncherSettings.BaseLauncherColumns._ID)) {
throw new RuntimeException("Error: attempting to add item without specifying an id");
}
+ helper.checkId(table, values);
return db.insert(table, nullColumnHack, values);
}
@@ -184,6 +200,20 @@ public class LauncherProvider extends ContentProvider {
}
@Override
+ public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
+ throws OperationApplicationException {
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ db.beginTransaction();
+ try {
+ ContentProviderResult[] result = super.applyBatch(operations);
+ db.setTransactionSuccessful();
+ return result;
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ @Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
@@ -266,15 +296,22 @@ public class LauncherProvider extends ContentProvider {
SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE);
if (sp.getBoolean(EMPTY_DATABASE_CREATED, false)) {
+ Log.d(TAG, "loading default workspace");
int workspaceResId = origWorkspaceResId;
// Use default workspace resource if none provided
if (workspaceResId == 0) {
TelephonyManager tm = (TelephonyManager) getContext().getSystemService(Context.TELEPHONY_SERVICE);
if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_NONE) {
- workspaceResId = sp.getInt(DEFAULT_WORKSPACE_RESOURCE_ID, R.xml.default_workspace_no_telephony);
+ if (areGAppsInstalled()) {
+ workspaceResId = sp.getInt(DEFAULT_WORKSPACE_RESOURCE_ID,
+ R.xml.default_workspace_no_telephony_gapps);
+ } else {
+ workspaceResId = sp.getInt(DEFAULT_WORKSPACE_RESOURCE_ID,
+ R.xml.default_workspace_no_telephony);
+ }
} else {
- workspaceResId = sp.getInt(DEFAULT_WORKSPACE_RESOURCE_ID, R.xml.default_workspace);
+ workspaceResId = sp.getInt(DEFAULT_WORKSPACE_RESOURCE_ID, getDefaultWorkspaceResourceId());
}
}
@@ -291,10 +328,59 @@ public class LauncherProvider extends ContentProvider {
}
}
+ private boolean areGAppsInstalled() {
+ PackageManager pm = getContext().getPackageManager();
+ try {
+ PackageInfo info = pm.getPackageInfo("com.google.android.gsf",PackageManager.GET_META_DATA);
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ return true;
+ }
+
+ public void migrateLauncher2Shortcuts() {
+ mOpenHelper.migrateLauncher2Shortcuts(mOpenHelper.getWritableDatabase(),
+ LauncherSettings.Favorites.OLD_CONTENT_URI);
+ }
+
+ private int getDefaultWorkspaceResourceId() {
+ if (LauncherAppState.isDisableAllApps()) {
+ if (areGAppsInstalled()){
+ return R.xml.default_workspace_no_all_apps_gapps;
+ } else {
+ return R.xml.default_workspace_no_all_apps;
+ }
+ } else {
+ if (areGAppsInstalled()){
+ return R.xml.default_workspace_gapps;
+ } else {
+ return R.xml.default_workspace;
+ }
+ }
+ }
+
private static interface ContentValuesCallback {
public void onRow(ContentValues values);
}
+ private static boolean shouldImportLauncher2Database(Context context) {
+ boolean isTablet = context.getResources().getBoolean(R.bool.is_tablet);
+
+ // We don't import the old databse for tablets, as the grid size has changed.
+ return !isTablet && IMPORT_LAUNCHER2_DATABASE;
+ }
+
+ public void deleteDatabase() {
+ // Are you sure? (y/n)
+ final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ final File dbFile = new File(db.getPath());
+ mOpenHelper.close();
+ if (dbFile.exists()) {
+ SQLiteDatabase.deleteDatabase(dbFile);
+ }
+ mOpenHelper = new DatabaseHelper(getContext());
+ }
+
private static class DatabaseHelper extends SQLiteOpenHelper {
private static final String TAG_FAVORITES = "favorites";
private static final String TAG_FAVORITE = "favorite";
@@ -311,6 +397,8 @@ public class LauncherProvider extends ContentProvider {
private long mMaxItemId = -1;
private long mMaxScreenId = -1;
+ private boolean mNewDbCreated = false;
+
DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
mContext = context;
@@ -326,6 +414,10 @@ public class LauncherProvider extends ContentProvider {
}
}
+ public boolean wasNewDbCreated() {
+ return mNewDbCreated;
+ }
+
/**
* Send notification that we've deleted the {@link AppWidgetHost},
* probably as part of the initial database creation. The receiver may
@@ -343,6 +435,7 @@ public class LauncherProvider extends ContentProvider {
mMaxItemId = 1;
mMaxScreenId = 0;
+ mNewDbCreated = true;
db.execSQL("CREATE TABLE favorites (" +
"_id INTEGER PRIMARY KEY," +
@@ -364,7 +457,9 @@ public class LauncherProvider extends ContentProvider {
"uri TEXT," +
"displayMode INTEGER," +
"appWidgetProvider TEXT," +
- "modified INTEGER NOT NULL DEFAULT 0" +
+ "modified INTEGER NOT NULL DEFAULT 0," +
+ "restored INTEGER NOT NULL DEFAULT 0," +
+ "hidden INTEGER DEFAULT 0" +
");");
addWorkspacesTable(db);
@@ -374,32 +469,38 @@ public class LauncherProvider extends ContentProvider {
sendAppWidgetResetNotify();
}
- // Try converting the old database
- ContentValuesCallback permuteScreensCb = new ContentValuesCallback() {
- public void onRow(ContentValues values) {
- int container = values.getAsInteger(LauncherSettings.Favorites.CONTAINER);
- if (container == Favorites.CONTAINER_DESKTOP) {
- int screen = values.getAsInteger(LauncherSettings.Favorites.SCREEN);
- screen = (int) upgradeLauncherDb_permuteScreens(screen);
- values.put(LauncherSettings.Favorites.SCREEN, screen);
+ if (shouldImportLauncher2Database(mContext)) {
+ // Try converting the old database
+ ContentValuesCallback permuteScreensCb = new ContentValuesCallback() {
+ public void onRow(ContentValues values) {
+ int container = values.getAsInteger(LauncherSettings.Favorites.CONTAINER);
+ if (container == Favorites.CONTAINER_DESKTOP) {
+ int screen = values.getAsInteger(LauncherSettings.Favorites.SCREEN);
+ screen = (int) upgradeLauncherDb_permuteScreens(screen);
+ values.put(LauncherSettings.Favorites.SCREEN, screen);
+ }
+ }
+ };
+ Uri uri = Uri.parse("content://" + Settings.AUTHORITY +
+ "/old_favorites?notify=true");
+ if (!convertDatabase(db, uri, permuteScreensCb, true)) {
+ // Try and upgrade from the Launcher2 db
+ uri = LauncherSettings.Favorites.OLD_CONTENT_URI;
+ if (!convertDatabase(db, uri, permuteScreensCb, false)) {
+ // If we fail, then set a flag to load the default workspace
+ setFlagEmptyDbCreated();
+ return;
}
}
- };
- Uri uri = Uri.parse("content://" + Settings.AUTHORITY +
- "/old_favorites?notify=true");
- if (!convertDatabase(db, uri, permuteScreensCb, true)) {
- // Try and upgrade from the Launcher2 db
- uri = LauncherSettings.Favorites.OLD_CONTENT_URI;
- if (!convertDatabase(db, uri, permuteScreensCb, false)) {
- // If we fail, then set a flag to load the default workspace
- setFlagEmptyDbCreated();
- return;
- }
+ // Right now, in non-default workspace cases, we want to run the final
+ // upgrade code (ie. to fix workspace screen indices -> ids, etc.), so
+ // set that flag too.
+ setFlagJustLoadedOldDb();
+ } else {
+ // Fresh and clean launcher DB.
+ mMaxItemId = initializeMaxItemId(db);
+ setFlagEmptyDbCreated();
}
- // Right now, in non-default workspace cases, we want to run the final
- // upgrade code (ie. to fix workspace screen indices -> ids, etc.), so
- // set that flag too.
- setFlagJustLoadedOldDb();
}
private void addWorkspacesTable(SQLiteDatabase db) {
@@ -670,7 +771,6 @@ public class LauncherProvider extends ContentProvider {
}
}
-
if (version < 15) {
db.beginTransaction();
try {
@@ -747,7 +847,6 @@ public class LauncherProvider extends ContentProvider {
}
}
-
// Artificially inflate the version to make sure we're fully up to date
// after a possible 10.2-Trebuchet migration
@@ -755,6 +854,37 @@ public class LauncherProvider extends ContentProvider {
version = 17;
}
+ if (version < 18) {
+ db.beginTransaction();
+ try {
+ // Insert new column for holding restore status
+ db.execSQL("ALTER TABLE favorites " +
+ "ADD COLUMN restored INTEGER NOT NULL DEFAULT 0;");
+ db.setTransactionSuccessful();
+ version = 18;
+ } catch (SQLException ex) {
+ // Old version remains, which means we wipe old data
+ Log.e(TAG, ex.getMessage(), ex);
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ if (version < 19) {
+ // We use the db version upgrade here to identify users who may not have seen
+ // clings yet (because they weren't available), but for whom the clings are now
+ // available (tablet users). Because one of the possible cling flows (migration)
+ // is very destructive (wipes out workspaces), we want to prevent this from showing
+ // until clear data. We do so by marking that the clings have been shown.
+ LauncherClings.synchonouslyMarkFirstRunClingDismissed(mContext);
+ version = 19;
+ }
+
+ if (oldVersion < 20) {
+ db.execSQL("ALTER TABLE favorites ADD hidden INTEGER DEFAULT 0");
+ version = 20;
+ }
+
if (version != DATABASE_VERSION) {
Log.w(TAG, "Destroying all old data.");
db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES);
@@ -917,6 +1047,15 @@ public class LauncherProvider extends ContentProvider {
mMaxItemId = id + 1;
}
+ public void checkId(String table, ContentValues values) {
+ long id = values.getAsLong(LauncherSettings.BaseLauncherColumns._ID);
+ if (table == LauncherProvider.TABLE_WORKSPACE_SCREENS) {
+ mMaxScreenId = Math.max(id, mMaxScreenId);
+ } else {
+ mMaxItemId = Math.max(id, mMaxItemId);
+ }
+ }
+
private long initializeMaxItemId(SQLiteDatabase db) {
Cursor c = db.rawQuery("SELECT MAX(_id) FROM favorites", null);
@@ -947,10 +1086,14 @@ public class LauncherProvider extends ContentProvider {
throw new RuntimeException("Error: max screen id was not initialized");
}
mMaxScreenId += 1;
+ // Log to disk
+ Launcher.addDumpLog(TAG, "11683562 - generateNewScreenId(): " + mMaxScreenId, true);
return mMaxScreenId;
}
public void updateMaxScreenId(long maxScreenId) {
+ // Log to disk
+ Launcher.addDumpLog(TAG, "11683562 - updateMaxScreenId(): " + maxScreenId, true);
mMaxScreenId = maxScreenId;
}
@@ -971,6 +1114,8 @@ public class LauncherProvider extends ContentProvider {
throw new RuntimeException("Error: could not query max screen id");
}
+ // Log to disk
+ Launcher.addDumpLog(TAG, "11683562 - initializeMaxScreenId(): " + id, true);
return id;
}
@@ -1102,10 +1247,6 @@ public class LauncherProvider extends ContentProvider {
final int depth = parser.getDepth();
- final HashMap<Long, ItemInfo[][]> occupied = new HashMap<Long, ItemInfo[][]>();
- LauncherModel model = LauncherAppState.getInstance().getModel();
- AtomicBoolean deleteItem = new AtomicBoolean();
-
int type;
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
@@ -1129,7 +1270,6 @@ public class LauncherProvider extends ContentProvider {
// recursively load some more favorites, why not?
i += loadFavorites(db, resId);
added = false;
- mMaxItemId = -1;
} else {
Log.w(TAG, String.format("Skipping <include workspace=0x%08x>", resId));
}
@@ -1158,18 +1298,6 @@ public class LauncherProvider extends ContentProvider {
values.put(LauncherSettings.Favorites.CELLX, x);
values.put(LauncherSettings.Favorites.CELLY, y);
- ItemInfo info = new ItemInfo();
- info.container = container;
- info.spanX = a.getInt(R.styleable.Favorite_spanX, 1);
- info.spanY = a.getInt(R.styleable.Favorite_spanY, 1);
- info.cellX = a.getInt(R.styleable.Favorite_x, 0);
- info.cellY = a.getInt(R.styleable.Favorite_y, 0);
- info.screenId = a.getInt(R.styleable.Favorite_screen, 0);
-
- if (!model.checkItemPlacement(occupied, info, deleteItem)) {
- continue;
- }
-
if (LOGD) {
final String title = a.getString(R.styleable.Favorite_title);
final String pkg = a.getString(R.styleable.Favorite_packageName);
@@ -1256,22 +1384,7 @@ public class LauncherProvider extends ContentProvider {
added = false;
}
}
- if (added) {
- i++;
- } else {
- long containerIndex = info.screenId;
- if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
- occupied.get((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT)
- [(int) info.screenId][0] = null;
- } else {
- ItemInfo[][] screens = occupied.get(info.screenId);
- for (int gridX = info.cellX; gridX < (info.cellX+info.spanX); gridX++) {
- for (int gridY = info.cellY; gridY < (info.cellY+info.spanY); gridY++) {
- screens[gridX][gridY] = null;
- }
- }
- }
- }
+ if (added) i++;
a.recycle();
}
} catch (XmlPullParserException e) {
@@ -1511,6 +1624,273 @@ public class LauncherProvider extends ContentProvider {
}
return id;
}
+
+ public void migrateLauncher2Shortcuts(SQLiteDatabase db, Uri uri) {
+ final ContentResolver resolver = mContext.getContentResolver();
+ Cursor c = null;
+ int count = 0;
+ int curScreen = 0;
+
+ try {
+ c = resolver.query(uri, null, null, null, "title ASC");
+ } catch (Exception e) {
+ // Ignore
+ }
+
+ // We already have a favorites database in the old provider
+ if (c != null) {
+ try {
+ if (c.getCount() > 0) {
+ final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
+ final int intentIndex
+ = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
+ final int titleIndex
+ = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
+ final int iconTypeIndex
+ = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE);
+ final int iconIndex
+ = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
+ final int iconPackageIndex
+ = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE);
+ final int iconResourceIndex
+ = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE);
+ final int containerIndex
+ = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
+ final int itemTypeIndex
+ = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
+ final int screenIndex
+ = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
+ final int cellXIndex
+ = 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);
+
+ int i = 0;
+ int curX = 0;
+ int curY = 0;
+
+ final LauncherAppState app = LauncherAppState.getInstance();
+ final DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+ final int width = (int) grid.numColumns;
+ final int height = (int) grid.numRows;
+ final int hotseatWidth = (int) grid.numHotseatIcons;
+ PackageManager pm = mContext.getPackageManager();
+
+ final HashSet<String> seenIntents = new HashSet<String>(c.getCount());
+
+ final ArrayList<ContentValues> shortcuts = new ArrayList<ContentValues>();
+ final ArrayList<ContentValues> folders = new ArrayList<ContentValues>();
+ final SparseArray<ContentValues> hotseat = new SparseArray<ContentValues>();
+
+ while (c.moveToNext()) {
+ final int itemType = c.getInt(itemTypeIndex);
+ if (itemType != Favorites.ITEM_TYPE_APPLICATION
+ && itemType != Favorites.ITEM_TYPE_SHORTCUT
+ && itemType != Favorites.ITEM_TYPE_FOLDER) {
+ continue;
+ }
+
+ final int cellX = c.getInt(cellXIndex);
+ final int cellY = c.getInt(cellYIndex);
+ final int screen = c.getInt(screenIndex);
+ int container = c.getInt(containerIndex);
+ final String intentStr = c.getString(intentIndex);
+ Launcher.addDumpLog(TAG, "migrating \""
+ + c.getString(titleIndex) + "\" ("
+ + cellX + "," + cellY + "@"
+ + LauncherSettings.Favorites.containerToString(container)
+ + "/" + screen
+ + "): " + intentStr, true);
+
+ if (itemType != Favorites.ITEM_TYPE_FOLDER) {
+
+ final Intent intent;
+ final ComponentName cn;
+ try {
+ intent = Intent.parseUri(intentStr, 0);
+ } catch (URISyntaxException e) {
+ // bogus intent?
+ Launcher.addDumpLog(TAG,
+ "skipping invalid intent uri", true);
+ continue;
+ }
+
+ cn = intent.getComponent();
+ if (TextUtils.isEmpty(intentStr)) {
+ // no intent? no icon
+ Launcher.addDumpLog(TAG, "skipping empty intent", true);
+ continue;
+ } else if (cn != null &&
+ !LauncherModel.isValidPackageComponent(pm, cn)) {
+ // component no longer exists.
+ Launcher.addDumpLog(TAG, "skipping item whose component " +
+ "no longer exists.", true);
+ continue;
+ } else if (container ==
+ LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+ // Dedupe icons directly on the workspace
+
+ // Canonicalize
+ // the Play Store sets the package parameter, but Launcher
+ // does not, so we clear that out to keep them the same
+ intent.setPackage(null);
+ final String key = intent.toUri(0);
+ if (seenIntents.contains(key)) {
+ Launcher.addDumpLog(TAG, "skipping duplicate", true);
+ continue;
+ } else {
+ seenIntents.add(key);
+ }
+ }
+ }
+
+ ContentValues values = new ContentValues(c.getColumnCount());
+ values.put(LauncherSettings.Favorites._ID, c.getInt(idIndex));
+ values.put(LauncherSettings.Favorites.INTENT, intentStr);
+ values.put(LauncherSettings.Favorites.TITLE, c.getString(titleIndex));
+ values.put(LauncherSettings.Favorites.ICON_TYPE,
+ c.getInt(iconTypeIndex));
+ values.put(LauncherSettings.Favorites.ICON, c.getBlob(iconIndex));
+ values.put(LauncherSettings.Favorites.ICON_PACKAGE,
+ c.getString(iconPackageIndex));
+ values.put(LauncherSettings.Favorites.ICON_RESOURCE,
+ 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));
+
+ if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+ hotseat.put(screen, values);
+ }
+
+ if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+ // In a folder or in the hotseat, preserve position
+ values.put(LauncherSettings.Favorites.SCREEN, screen);
+ values.put(LauncherSettings.Favorites.CELLX, cellX);
+ values.put(LauncherSettings.Favorites.CELLY, cellY);
+ } else {
+ // For items contained directly on one of the workspace screen,
+ // we'll determine their location (screen, x, y) in a second pass.
+ }
+
+ values.put(LauncherSettings.Favorites.CONTAINER, container);
+
+ if (itemType != Favorites.ITEM_TYPE_FOLDER) {
+ shortcuts.add(values);
+ } else {
+ folders.add(values);
+ }
+ }
+
+ // Now that we have all the hotseat icons, let's go through them left-right
+ // and assign valid locations for them in the new hotseat
+ final int N = hotseat.size();
+ for (int idx=0; idx<N; idx++) {
+ int hotseatX = hotseat.keyAt(idx);
+ ContentValues values = hotseat.valueAt(idx);
+
+ if (hotseatX == grid.hotseatAllAppsRank) {
+ // let's drop this in the next available hole in the hotseat
+ while (++hotseatX < hotseatWidth) {
+ if (hotseat.get(hotseatX) == null) {
+ // found a spot! move it here
+ values.put(LauncherSettings.Favorites.SCREEN,
+ hotseatX);
+ break;
+ }
+ }
+ }
+ if (hotseatX >= hotseatWidth) {
+ // no room for you in the hotseat? it's off to the desktop with you
+ values.put(LauncherSettings.Favorites.CONTAINER,
+ Favorites.CONTAINER_DESKTOP);
+ }
+ }
+
+ final ArrayList<ContentValues> allItems = new ArrayList<ContentValues>();
+ // Folders first
+ allItems.addAll(folders);
+ // Then shortcuts
+ allItems.addAll(shortcuts);
+
+ // Layout all the folders
+ for (ContentValues values: allItems) {
+ if (values.getAsInteger(LauncherSettings.Favorites.CONTAINER) !=
+ LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+ // Hotseat items and folder items have already had their
+ // location information set. Nothing to be done here.
+ continue;
+ }
+ values.put(LauncherSettings.Favorites.SCREEN, curScreen);
+ values.put(LauncherSettings.Favorites.CELLX, curX);
+ values.put(LauncherSettings.Favorites.CELLY, curY);
+ curX = (curX + 1) % width;
+ if (curX == 0) {
+ curY = (curY + 1);
+ }
+ // Leave the last row of icons blank on every screen
+ if (curY == height - 1) {
+ curScreen = (int) generateNewScreenId();
+ curY = 0;
+ }
+ }
+
+ if (allItems.size() > 0) {
+ db.beginTransaction();
+ try {
+ for (ContentValues row: allItems) {
+ if (row == null) continue;
+ if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, row)
+ < 0) {
+ return;
+ } else {
+ count++;
+ }
+ }
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ db.beginTransaction();
+ try {
+ for (i=0; i<=curScreen; i++) {
+ final ContentValues values = new ContentValues();
+ values.put(LauncherSettings.WorkspaceScreens._ID, i);
+ values.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
+ if (dbInsertAndCheck(this, db, TABLE_WORKSPACE_SCREENS, null, values)
+ < 0) {
+ return;
+ }
+ }
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ }
+ } finally {
+ c.close();
+ }
+ }
+
+ Launcher.addDumpLog(TAG, "migrated " + count + " icons from Launcher2 into "
+ + (curScreen+1) + " screens", true);
+
+ // ensure that new screens are created to hold these icons
+ setFlagJustLoadedOldDb();
+
+ // Update max IDs; very important since we just grabbed IDs from another database
+ mMaxItemId = initializeMaxItemId(db);
+ mMaxScreenId = initializeMaxScreenId(db);
+ if (LOGD) Log.d(TAG, "mMaxItemId: " + mMaxItemId + " mMaxScreenId: " + mMaxScreenId);
+ }
}
/**
diff --git a/src/com/android/launcher3/LauncherScroller.java b/src/com/android/launcher3/LauncherScroller.java
new file mode 100644
index 000000000..3bd0a78c4
--- /dev/null
+++ b/src/com/android/launcher3/LauncherScroller.java
@@ -0,0 +1,559 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.animation.TimeInterpolator;
+import android.content.Context;
+import android.hardware.SensorManager;
+import android.os.Build;
+import android.util.FloatMath;
+import android.view.ViewConfiguration;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+
+/**
+ * This class differs from the framework {@link android.widget.Scroller} in that
+ * you can modify the Interpolator post-construction.
+ */
+public class LauncherScroller {
+ private int mMode;
+
+ private int mStartX;
+ private int mStartY;
+ private int mFinalX;
+ private int mFinalY;
+
+ private int mMinX;
+ private int mMaxX;
+ private int mMinY;
+ private int mMaxY;
+
+ private int mCurrX;
+ private int mCurrY;
+ private long mStartTime;
+ private int mDuration;
+ private float mDurationReciprocal;
+ private float mDeltaX;
+ private float mDeltaY;
+ private boolean mFinished;
+ private TimeInterpolator mInterpolator;
+ private boolean mFlywheel;
+
+ private float mVelocity;
+ private float mCurrVelocity;
+ private int mDistance;
+
+ private float mFlingFriction = ViewConfiguration.getScrollFriction();
+
+ private static final int DEFAULT_DURATION = 250;
+ private static final int SCROLL_MODE = 0;
+ private static final int FLING_MODE = 1;
+
+ private static float DECELERATION_RATE = (float) (Math.log(0.78) / Math.log(0.9));
+ private static final float INFLEXION = 0.35f; // Tension lines cross at (INFLEXION, 1)
+ private static final float START_TENSION = 0.5f;
+ private static final float END_TENSION = 1.0f;
+ private static final float P1 = START_TENSION * INFLEXION;
+ private static final float P2 = 1.0f - END_TENSION * (1.0f - INFLEXION);
+
+ private static final int NB_SAMPLES = 100;
+ private static final float[] SPLINE_POSITION = new float[NB_SAMPLES + 1];
+ private static final float[] SPLINE_TIME = new float[NB_SAMPLES + 1];
+
+ private float mDeceleration;
+ private final float mPpi;
+
+ // A context-specific coefficient adjusted to physical values.
+ private float mPhysicalCoeff;
+
+ static {
+ float x_min = 0.0f;
+ float y_min = 0.0f;
+ for (int i = 0; i < NB_SAMPLES; i++) {
+ final float alpha = (float) i / NB_SAMPLES;
+
+ float x_max = 1.0f;
+ float x, tx, coef;
+ while (true) {
+ x = x_min + (x_max - x_min) / 2.0f;
+ coef = 3.0f * x * (1.0f - x);
+ tx = coef * ((1.0f - x) * P1 + x * P2) + x * x * x;
+ if (Math.abs(tx - alpha) < 1E-5) break;
+ if (tx > alpha) x_max = x;
+ else x_min = x;
+ }
+ SPLINE_POSITION[i] = coef * ((1.0f - x) * START_TENSION + x) + x * x * x;
+
+ float y_max = 1.0f;
+ float y, dy;
+ while (true) {
+ y = y_min + (y_max - y_min) / 2.0f;
+ coef = 3.0f * y * (1.0f - y);
+ dy = coef * ((1.0f - y) * START_TENSION + y) + y * y * y;
+ if (Math.abs(dy - alpha) < 1E-5) break;
+ if (dy > alpha) y_max = y;
+ else y_min = y;
+ }
+ SPLINE_TIME[i] = coef * ((1.0f - y) * P1 + y * P2) + y * y * y;
+ }
+ SPLINE_POSITION[NB_SAMPLES] = SPLINE_TIME[NB_SAMPLES] = 1.0f;
+
+ // This controls the viscous fluid effect (how much of it)
+ sViscousFluidScale = 8.0f;
+ // must be set to 1.0 (used in viscousFluid())
+ sViscousFluidNormalize = 1.0f;
+ sViscousFluidNormalize = 1.0f / viscousFluid(1.0f);
+
+ }
+
+ private static float sViscousFluidScale;
+ private static float sViscousFluidNormalize;
+
+ public void setInterpolator(TimeInterpolator interpolator) {
+ mInterpolator = interpolator;
+ }
+
+ /**
+ * Create a Scroller with the default duration and interpolator.
+ */
+ public LauncherScroller(Context context) {
+ this(context, null);
+ }
+
+ /**
+ * Create a Scroller with the specified interpolator. If the interpolator is
+ * null, the default (viscous) interpolator will be used. "Flywheel" behavior will
+ * be in effect for apps targeting Honeycomb or newer.
+ */
+ public LauncherScroller(Context context, Interpolator interpolator) {
+ this(context, interpolator,
+ context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB);
+ }
+
+ /**
+ * Create a Scroller with the specified interpolator. If the interpolator is
+ * null, the default (viscous) interpolator will be used. Specify whether or
+ * not to support progressive "flywheel" behavior in flinging.
+ */
+ public LauncherScroller(Context context, Interpolator interpolator, boolean flywheel) {
+ mFinished = true;
+ mInterpolator = interpolator;
+ mPpi = context.getResources().getDisplayMetrics().density * 160.0f;
+ mDeceleration = computeDeceleration(ViewConfiguration.getScrollFriction());
+ mFlywheel = flywheel;
+
+ mPhysicalCoeff = computeDeceleration(0.84f); // look and feel tuning
+ }
+
+ /**
+ * The amount of friction applied to flings. The default value
+ * is {@link ViewConfiguration#getScrollFriction}.
+ *
+ * @param friction A scalar dimension-less value representing the coefficient of
+ * friction.
+ */
+ public final void setFriction(float friction) {
+ mDeceleration = computeDeceleration(friction);
+ mFlingFriction = friction;
+ }
+
+ private float computeDeceleration(float friction) {
+ return SensorManager.GRAVITY_EARTH // g (m/s^2)
+ * 39.37f // inch/meter
+ * mPpi // pixels per inch
+ * friction;
+ }
+
+ /**
+ *
+ * Returns whether the scroller has finished scrolling.
+ *
+ * @return True if the scroller has finished scrolling, false otherwise.
+ */
+ public final boolean isFinished() {
+ return mFinished;
+ }
+
+ /**
+ * Force the finished field to a particular value.
+ *
+ * @param finished The new finished value.
+ */
+ public final void forceFinished(boolean finished) {
+ mFinished = finished;
+ }
+
+ /**
+ * Returns how long the scroll event will take, in milliseconds.
+ *
+ * @return The duration of the scroll in milliseconds.
+ */
+ public final int getDuration() {
+ return mDuration;
+ }
+
+ /**
+ * Returns the current X offset in the scroll.
+ *
+ * @return The new X offset as an absolute distance from the origin.
+ */
+ public final int getCurrX() {
+ return mCurrX;
+ }
+
+ /**
+ * Returns the current Y offset in the scroll.
+ *
+ * @return The new Y offset as an absolute distance from the origin.
+ */
+ public final int getCurrY() {
+ return mCurrY;
+ }
+
+ /**
+ * Returns the current velocity.
+ *
+ * @return The original velocity less the deceleration. Result may be
+ * negative.
+ */
+ public float getCurrVelocity() {
+ return mMode == FLING_MODE ?
+ mCurrVelocity : mVelocity - mDeceleration * timePassed() / 2000.0f;
+ }
+
+ /**
+ * Returns the start X offset in the scroll.
+ *
+ * @return The start X offset as an absolute distance from the origin.
+ */
+ public final int getStartX() {
+ return mStartX;
+ }
+
+ /**
+ * Returns the start Y offset in the scroll.
+ *
+ * @return The start Y offset as an absolute distance from the origin.
+ */
+ public final int getStartY() {
+ return mStartY;
+ }
+
+ /**
+ * Returns where the scroll will end. Valid only for "fling" scrolls.
+ *
+ * @return The final X offset as an absolute distance from the origin.
+ */
+ public final int getFinalX() {
+ return mFinalX;
+ }
+
+ /**
+ * Returns where the scroll will end. Valid only for "fling" scrolls.
+ *
+ * @return The final Y offset as an absolute distance from the origin.
+ */
+ public final int getFinalY() {
+ return mFinalY;
+ }
+
+ /**
+ * Call this when you want to know the new location. If it returns true,
+ * the animation is not yet finished.
+ */
+ public boolean computeScrollOffset() {
+ if (mFinished) {
+ return false;
+ }
+
+ int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
+
+ if (timePassed < mDuration) {
+ switch (mMode) {
+ case SCROLL_MODE:
+ float x = timePassed * mDurationReciprocal;
+
+ if (mInterpolator == null)
+ x = viscousFluid(x);
+ else
+ x = mInterpolator.getInterpolation(x);
+
+ mCurrX = mStartX + Math.round(x * mDeltaX);
+ mCurrY = mStartY + Math.round(x * mDeltaY);
+ break;
+ case FLING_MODE:
+ final float t = (float) timePassed / mDuration;
+ final int index = (int) (NB_SAMPLES * t);
+ float distanceCoef = 1.f;
+ float velocityCoef = 0.f;
+ if (index < NB_SAMPLES) {
+ final float t_inf = (float) index / NB_SAMPLES;
+ final float t_sup = (float) (index + 1) / NB_SAMPLES;
+ final float d_inf = SPLINE_POSITION[index];
+ final float d_sup = SPLINE_POSITION[index + 1];
+ velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);
+ distanceCoef = d_inf + (t - t_inf) * velocityCoef;
+ }
+
+ mCurrVelocity = velocityCoef * mDistance / mDuration * 1000.0f;
+
+ mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));
+ // Pin to mMinX <= mCurrX <= mMaxX
+ mCurrX = Math.min(mCurrX, mMaxX);
+ mCurrX = Math.max(mCurrX, mMinX);
+
+ mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));
+ // Pin to mMinY <= mCurrY <= mMaxY
+ mCurrY = Math.min(mCurrY, mMaxY);
+ mCurrY = Math.max(mCurrY, mMinY);
+
+ if (mCurrX == mFinalX && mCurrY == mFinalY) {
+ mFinished = true;
+ }
+
+ break;
+ }
+ }
+ else {
+ mCurrX = mFinalX;
+ mCurrY = mFinalY;
+ mFinished = true;
+ }
+ return true;
+ }
+
+ /**
+ * Start scrolling by providing a starting point and the distance to travel.
+ * The scroll will use the default value of 250 milliseconds for the
+ * duration.
+ *
+ * @param startX Starting horizontal scroll offset in pixels. Positive
+ * numbers will scroll the content to the left.
+ * @param startY Starting vertical scroll offset in pixels. Positive numbers
+ * will scroll the content up.
+ * @param dx Horizontal distance to travel. Positive numbers will scroll the
+ * content to the left.
+ * @param dy Vertical distance to travel. Positive numbers will scroll the
+ * content up.
+ */
+ public void startScroll(int startX, int startY, int dx, int dy) {
+ startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
+ }
+
+ /**
+ * Start scrolling by providing a starting point, the distance to travel,
+ * and the duration of the scroll.
+ *
+ * @param startX Starting horizontal scroll offset in pixels. Positive
+ * numbers will scroll the content to the left.
+ * @param startY Starting vertical scroll offset in pixels. Positive numbers
+ * will scroll the content up.
+ * @param dx Horizontal distance to travel. Positive numbers will scroll the
+ * content to the left.
+ * @param dy Vertical distance to travel. Positive numbers will scroll the
+ * content up.
+ * @param duration Duration of the scroll in milliseconds.
+ */
+ public void startScroll(int startX, int startY, int dx, int dy, int duration) {
+ mMode = SCROLL_MODE;
+ mFinished = false;
+ mDuration = duration;
+ mStartTime = AnimationUtils.currentAnimationTimeMillis();
+ mStartX = startX;
+ mStartY = startY;
+ mFinalX = startX + dx;
+ mFinalY = startY + dy;
+ mDeltaX = dx;
+ mDeltaY = dy;
+ mDurationReciprocal = 1.0f / (float) mDuration;
+ }
+
+ /**
+ * Start scrolling based on a fling gesture. The distance travelled will
+ * depend on the initial velocity of the fling.
+ *
+ * @param startX Starting point of the scroll (X)
+ * @param startY Starting point of the scroll (Y)
+ * @param velocityX Initial velocity of the fling (X) measured in pixels per
+ * second.
+ * @param velocityY Initial velocity of the fling (Y) measured in pixels per
+ * second
+ * @param minX Minimum X value. The scroller will not scroll past this
+ * point.
+ * @param maxX Maximum X value. The scroller will not scroll past this
+ * point.
+ * @param minY Minimum Y value. The scroller will not scroll past this
+ * point.
+ * @param maxY Maximum Y value. The scroller will not scroll past this
+ * point.
+ */
+ public void fling(int startX, int startY, int velocityX, int velocityY,
+ int minX, int maxX, int minY, int maxY) {
+ // Continue a scroll or fling in progress
+ if (mFlywheel && !mFinished) {
+ float oldVel = getCurrVelocity();
+
+ float dx = (float) (mFinalX - mStartX);
+ float dy = (float) (mFinalY - mStartY);
+ float hyp = FloatMath.sqrt(dx * dx + dy * dy);
+
+ float ndx = dx / hyp;
+ float ndy = dy / hyp;
+
+ float oldVelocityX = ndx * oldVel;
+ float oldVelocityY = ndy * oldVel;
+ if (Math.signum(velocityX) == Math.signum(oldVelocityX) &&
+ Math.signum(velocityY) == Math.signum(oldVelocityY)) {
+ velocityX += oldVelocityX;
+ velocityY += oldVelocityY;
+ }
+ }
+
+ mMode = FLING_MODE;
+ mFinished = false;
+
+ float velocity = FloatMath.sqrt(velocityX * velocityX + velocityY * velocityY);
+
+ mVelocity = velocity;
+ mDuration = getSplineFlingDuration(velocity);
+ mStartTime = AnimationUtils.currentAnimationTimeMillis();
+ mStartX = startX;
+ mStartY = startY;
+
+ float coeffX = velocity == 0 ? 1.0f : velocityX / velocity;
+ float coeffY = velocity == 0 ? 1.0f : velocityY / velocity;
+
+ double totalDistance = getSplineFlingDistance(velocity);
+ mDistance = (int) (totalDistance * Math.signum(velocity));
+
+ mMinX = minX;
+ mMaxX = maxX;
+ mMinY = minY;
+ mMaxY = maxY;
+
+ mFinalX = startX + (int) Math.round(totalDistance * coeffX);
+ // Pin to mMinX <= mFinalX <= mMaxX
+ mFinalX = Math.min(mFinalX, mMaxX);
+ mFinalX = Math.max(mFinalX, mMinX);
+
+ mFinalY = startY + (int) Math.round(totalDistance * coeffY);
+ // Pin to mMinY <= mFinalY <= mMaxY
+ mFinalY = Math.min(mFinalY, mMaxY);
+ mFinalY = Math.max(mFinalY, mMinY);
+ }
+
+ private double getSplineDeceleration(float velocity) {
+ return Math.log(INFLEXION * Math.abs(velocity) / (mFlingFriction * mPhysicalCoeff));
+ }
+
+ private int getSplineFlingDuration(float velocity) {
+ final double l = getSplineDeceleration(velocity);
+ final double decelMinusOne = DECELERATION_RATE - 1.0;
+ return (int) (1000.0 * Math.exp(l / decelMinusOne));
+ }
+
+ private double getSplineFlingDistance(float velocity) {
+ final double l = getSplineDeceleration(velocity);
+ final double decelMinusOne = DECELERATION_RATE - 1.0;
+ return mFlingFriction * mPhysicalCoeff * Math.exp(DECELERATION_RATE / decelMinusOne * l);
+ }
+
+ static float viscousFluid(float x)
+ {
+ x *= sViscousFluidScale;
+ if (x < 1.0f) {
+ x -= (1.0f - (float)Math.exp(-x));
+ } else {
+ float start = 0.36787944117f; // 1/e == exp(-1)
+ x = 1.0f - (float)Math.exp(1.0f - x);
+ x = start + x * (1.0f - start);
+ }
+ x *= sViscousFluidNormalize;
+ return x;
+ }
+
+ /**
+ * Stops the animation. Contrary to {@link #forceFinished(boolean)},
+ * aborting the animating cause the scroller to move to the final x and y
+ * position
+ *
+ * @see #forceFinished(boolean)
+ */
+ public void abortAnimation() {
+ mCurrX = mFinalX;
+ mCurrY = mFinalY;
+ mFinished = true;
+ }
+
+ /**
+ * Extend the scroll animation. This allows a running animation to scroll
+ * further and longer, when used with {@link #setFinalX(int)} or {@link #setFinalY(int)}.
+ *
+ * @param extend Additional time to scroll in milliseconds.
+ * @see #setFinalX(int)
+ * @see #setFinalY(int)
+ */
+ public void extendDuration(int extend) {
+ int passed = timePassed();
+ mDuration = passed + extend;
+ mDurationReciprocal = 1.0f / mDuration;
+ mFinished = false;
+ }
+
+ /**
+ * Returns the time elapsed since the beginning of the scrolling.
+ *
+ * @return The elapsed time in milliseconds.
+ */
+ public int timePassed() {
+ return (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
+ }
+
+ /**
+ * Sets the final position (X) for this scroller.
+ *
+ * @param newX The new X offset as an absolute distance from the origin.
+ * @see #extendDuration(int)
+ * @see #setFinalY(int)
+ */
+ public void setFinalX(int newX) {
+ mFinalX = newX;
+ mDeltaX = mFinalX - mStartX;
+ mFinished = false;
+ }
+
+ /**
+ * Sets the final position (Y) for this scroller.
+ *
+ * @param newY The new Y offset as an absolute distance from the origin.
+ * @see #extendDuration(int)
+ * @see #setFinalX(int)
+ */
+ public void setFinalY(int newY) {
+ mFinalY = newY;
+ mDeltaY = mFinalY - mStartY;
+ mFinished = false;
+ }
+
+ /**
+ * @hide
+ */
+ public boolean isScrollingInDirection(float xvel, float yvel) {
+ return !mFinished && Math.signum(xvel) == Math.signum(mFinalX - mStartX) &&
+ Math.signum(yvel) == Math.signum(mFinalY - mStartY);
+ }
+}
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index 1d161d097..034ffbaa3 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -40,6 +40,11 @@ class LauncherSettings {
static final String TITLE = "title";
/**
+ * Folder Hidden status
+ */
+ static final String HIDDEN = "hidden";
+
+ /**
* The Intent URL of the gesture, describing what it points to. This
* value is given to {@link android.content.Intent#parseUri(String, int)} to create
* an Intent that can be launched.
@@ -171,6 +176,14 @@ class LauncherSettings {
static final int CONTAINER_DESKTOP = -100;
static final int CONTAINER_HOTSEAT = -101;
+ static final String containerToString(int container) {
+ switch (container) {
+ case CONTAINER_DESKTOP: return "desktop";
+ case CONTAINER_HOTSEAT: return "hotseat";
+ default: return String.valueOf(container);
+ }
+ }
+
/**
* The screen holding the favorite (if container is CONTAINER_DESKTOP)
* <P>Type: INTEGER</P>
@@ -280,5 +293,11 @@ class LauncherSettings {
* @see android.provider.LiveFolders#DISPLAY_MODE_LIST
*/
static final String DISPLAY_MODE = "displayMode";
+
+ /**
+ * Boolean indicating that his item was restored and not yet successfully bound.
+ * <P>Type: INTEGER</P>
+ */
+ static final String RESTORED = "restored";
}
}
diff --git a/src/com/android/gallery3d/exif/ExifInvalidFormatException.java b/src/com/android/launcher3/LauncherWallpaperPickerActivity.java
index bf923ec26..10fe013ee 100644
--- a/src/com/android/gallery3d/exif/ExifInvalidFormatException.java
+++ b/src/com/android/launcher3/LauncherWallpaperPickerActivity.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012 The Android Open Source Project
+ * 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.
@@ -14,10 +14,17 @@
* limitations under the License.
*/
-package com.android.gallery3d.exif;
+package com.android.launcher3;
-public class ExifInvalidFormatException extends Exception {
- public ExifInvalidFormatException(String meg) {
- super(meg);
+import android.content.Intent;
+
+public class LauncherWallpaperPickerActivity extends WallpaperPickerActivity {
+ @Override
+ public void startActivityForResultSafely(Intent intent, int requestCode) {
+ Utilities.startActivityForResultSafely(this, intent, requestCode);
+ }
+ @Override
+ public boolean enableRotation() {
+ return Utilities.isRotationEnabled(this);
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/launcher3/Lists.java b/src/com/android/launcher3/Lists.java
new file mode 100644
index 000000000..51f5dc272
--- /dev/null
+++ b/src/com/android/launcher3/Lists.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2007 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 java.util.ArrayList;
+import java.util.Collections;
+
+/**
+ * Provides static methods for creating {@code List} instances easily, and other
+ * utility methods for working with lists.
+ */
+public class Lists {
+
+ /**
+ * Creates an empty {@code ArrayList} instance.
+ *
+ * <p><b>Note:</b> if you only need an <i>immutable</i> empty List, use
+ * {@link Collections#emptyList} instead.
+ *
+ * @return a newly-created, initially-empty {@code ArrayList}
+ */
+ public static <E> ArrayList<E> newArrayList() {
+ return new ArrayList<E>();
+ }
+
+ /**
+ * Creates a resizable {@code ArrayList} instance containing the given
+ * elements.
+ *
+ * <p><b>Note:</b> due to a bug in javac 1.5.0_06, we cannot support the
+ * following:
+ *
+ * <p>{@code List<Base> list = Lists.newArrayList(sub1, sub2);}
+ *
+ * <p>where {@code sub1} and {@code sub2} are references to subtypes of
+ * {@code Base}, not of {@code Base} itself. To get around this, you must
+ * use:
+ *
+ * <p>{@code List<Base> list = Lists.<Base>newArrayList(sub1, sub2);}
+ *
+ * @param elements the elements that the list should contain, in order
+ * @return a newly-created {@code ArrayList} containing those elements
+ */
+ public static <E> ArrayList<E> newArrayList(E... elements) {
+ int capacity = (elements.length * 110) / 100 + 5;
+ ArrayList<E> list = new ArrayList<E>(capacity);
+ Collections.addAll(list, elements);
+ return list;
+ }
+}
diff --git a/src/com/android/launcher3/LiveWallpaperListAdapter.java b/src/com/android/launcher3/LiveWallpaperListAdapter.java
deleted file mode 100644
index 43d8cfe0c..000000000
--- a/src/com/android/launcher3/LiveWallpaperListAdapter.java
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3;
-
-import android.app.WallpaperInfo;
-import android.app.WallpaperManager;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.graphics.drawable.Drawable;
-import android.os.AsyncTask;
-import android.service.wallpaper.WallpaperService;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.ListAdapter;
-import android.widget.TextView;
-
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-import java.text.Collator;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-
-public class LiveWallpaperListAdapter extends BaseAdapter implements ListAdapter {
- private static final String LOG_TAG = "LiveWallpaperListAdapter";
-
- private final LayoutInflater mInflater;
- private final PackageManager mPackageManager;
-
- private List<LiveWallpaperTile> mWallpapers;
-
- @SuppressWarnings("unchecked")
- public LiveWallpaperListAdapter(Context context) {
- mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- mPackageManager = context.getPackageManager();
-
- List<ResolveInfo> list = mPackageManager.queryIntentServices(
- new Intent(WallpaperService.SERVICE_INTERFACE),
- PackageManager.GET_META_DATA);
-
- mWallpapers = new ArrayList<LiveWallpaperTile>();
-
- new LiveWallpaperEnumerator(context).execute(list);
- }
-
- public int getCount() {
- if (mWallpapers == null) {
- return 0;
- }
- return mWallpapers.size();
- }
-
- public LiveWallpaperTile getItem(int position) {
- return mWallpapers.get(position);
- }
-
- public long getItemId(int position) {
- return position;
- }
-
- public View getView(int position, View convertView, ViewGroup parent) {
- View view;
-
- if (convertView == null) {
- view = mInflater.inflate(R.layout.wallpaper_picker_live_wallpaper_item, parent, false);
- } else {
- view = convertView;
- }
-
- WallpaperPickerActivity.setWallpaperItemPaddingToZero((FrameLayout) view);
-
- LiveWallpaperTile wallpaperInfo = mWallpapers.get(position);
- wallpaperInfo.setView(view);
- ImageView image = (ImageView) view.findViewById(R.id.wallpaper_image);
- ImageView icon = (ImageView) view.findViewById(R.id.wallpaper_icon);
- if (wallpaperInfo.mThumbnail != null) {
- image.setImageDrawable(wallpaperInfo.mThumbnail);
- icon.setVisibility(View.GONE);
- } else {
- icon.setImageDrawable(wallpaperInfo.mInfo.loadIcon(mPackageManager));
- icon.setVisibility(View.VISIBLE);
- }
-
- TextView label = (TextView) view.findViewById(R.id.wallpaper_item_label);
- label.setText(wallpaperInfo.mInfo.loadLabel(mPackageManager));
-
- return view;
- }
-
- public static class LiveWallpaperTile extends WallpaperPickerActivity.WallpaperTileInfo {
- private Drawable mThumbnail;
- private WallpaperInfo mInfo;
- public LiveWallpaperTile(Drawable thumbnail, WallpaperInfo info, Intent intent) {
- mThumbnail = thumbnail;
- mInfo = info;
- }
- @Override
- public void onClick(WallpaperCropActivity a) {
- Intent preview = new Intent(WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER);
- preview.putExtra(WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT,
- mInfo.getComponent());
- a.onLiveWallpaperPickerLaunch();
- Utilities.startActivityForResultSafely(
- a, preview, WallpaperPickerActivity.PICK_LIVE_WALLPAPER);
- }
- }
-
- private class LiveWallpaperEnumerator extends
- AsyncTask<List<ResolveInfo>, LiveWallpaperTile, Void> {
- private Context mContext;
- private int mWallpaperPosition;
-
- public LiveWallpaperEnumerator(Context context) {
- super();
- mContext = context;
- mWallpaperPosition = 0;
- }
-
- @Override
- protected Void doInBackground(List<ResolveInfo>... params) {
- final PackageManager packageManager = mContext.getPackageManager();
-
- List<ResolveInfo> list = params[0];
-
- Collections.sort(list, new Comparator<ResolveInfo>() {
- final Collator mCollator;
-
- {
- mCollator = Collator.getInstance();
- }
-
- public int compare(ResolveInfo info1, ResolveInfo info2) {
- return mCollator.compare(info1.loadLabel(packageManager),
- info2.loadLabel(packageManager));
- }
- });
-
- for (ResolveInfo resolveInfo : list) {
- WallpaperInfo info = null;
- try {
- info = new WallpaperInfo(mContext, resolveInfo);
- } catch (XmlPullParserException e) {
- Log.w(LOG_TAG, "Skipping wallpaper " + resolveInfo.serviceInfo, e);
- continue;
- } catch (IOException e) {
- Log.w(LOG_TAG, "Skipping wallpaper " + resolveInfo.serviceInfo, e);
- continue;
- }
-
-
- Drawable thumb = info.loadThumbnail(packageManager);
- Intent launchIntent = new Intent(WallpaperService.SERVICE_INTERFACE);
- launchIntent.setClassName(info.getPackageName(), info.getServiceName());
- LiveWallpaperTile wallpaper = new LiveWallpaperTile(thumb, info, launchIntent);
- publishProgress(wallpaper);
- }
- // Send a null object to show loading is finished
- publishProgress((LiveWallpaperTile) null);
-
- return null;
- }
-
- @Override
- protected void onProgressUpdate(LiveWallpaperTile...infos) {
- for (LiveWallpaperTile info : infos) {
- if (info == null) {
- LiveWallpaperListAdapter.this.notifyDataSetChanged();
- break;
- }
- info.mThumbnail.setDither(true);
- if (mWallpaperPosition < mWallpapers.size()) {
- mWallpapers.set(mWallpaperPosition, info);
- } else {
- mWallpapers.add(info);
- }
- mWallpaperPosition++;
- }
- }
- }
-}
diff --git a/src/com/android/launcher3/LockWallpaperPickerActivity.java b/src/com/android/launcher3/LockWallpaperPickerActivity.java
deleted file mode 100644
index 483568480..000000000
--- a/src/com/android/launcher3/LockWallpaperPickerActivity.java
+++ /dev/null
@@ -1,1600 +0,0 @@
-/*
- * Copyright (C) 2014 The CyanogenMod Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3;
-
-import android.animation.Animator;
-import android.animation.LayoutTransition;
-import android.app.ActionBar;
-import android.app.Activity;
-import android.app.WallpaperInfo;
-import android.app.WallpaperManager;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.AssetManager;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.CompressFormat;
-import android.graphics.BitmapFactory;
-import android.graphics.BitmapRegionDecoder;
-import android.graphics.Canvas;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.Point;
-import android.graphics.PorterDuff;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.LevelListDrawable;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.provider.MediaStore;
-import android.provider.ThemesContract.ThemesColumns;
-import android.util.Log;
-import android.util.Pair;
-import android.view.ActionMode;
-import android.view.Display;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
-import android.view.ViewTreeObserver.OnGlobalLayoutListener;
-import android.view.animation.AccelerateInterpolator;
-import android.view.animation.DecelerateInterpolator;
-import android.widget.BaseAdapter;
-import android.widget.FrameLayout;
-import android.widget.HorizontalScrollView;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.ListAdapter;
-
-import com.android.gallery3d.common.Utils;
-import com.android.launcher3.WallpaperCropActivity.BitmapCropTask;
-import com.android.launcher3.WallpaperCropActivity.OnBitmapCroppedHandler;
-import com.android.photos.BitmapRegionTileSource;
-
-import java.io.BufferedInputStream;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-
-public class LockWallpaperPickerActivity extends WallpaperCropActivity {
- static final String TAG = LockWallpaperPickerActivity.class.getSimpleName();
-
- public static final int IMAGE_PICK = 5;
- public static final int PICK_WALLPAPER_THIRD_PARTY_ACTIVITY = 6;
- public static final int PICK_LIVE_WALLPAPER = 7;
- private static final String TEMP_WALLPAPER_TILES = "TEMP_KEYGUARD_WALLPAPER_TILES";
-
- private View mSelectedThumb;
- private boolean mIgnoreNextTap;
- private OnClickListener mThumbnailOnClickListener;
-
- private LinearLayout mWallpapersView;
- private View mWallpaperStrip;
-
- private ActionMode.Callback mActionModeCallback;
- private ActionMode mActionMode;
-
- private View.OnLongClickListener mLongClickListener;
-
- ArrayList<Uri> mTempWallpaperTiles = new ArrayList<Uri>();
- private SavedWallpaperImages mSavedImages;
-
- public static class PickImageInfo extends WallpaperTileInfo {
- @Override
- public void onClick(WallpaperCropActivity a) {
- Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
- intent.setType("image/*");
- Utilities.startActivityForResultSafely(((LockWallpaperPickerActivity)a), intent, IMAGE_PICK);
- }
- }
-
- public static class UserDesktopWallpaperInfo extends WallpaperTileInfo {
- @Override
- public void onClick(WallpaperCropActivity a) {
- WallpaperManager am = WallpaperManager.getInstance(a);
- am.clearKeyguardWallpaper();
- a.setResult(RESULT_OK);
- a.finish();
- }
- }
-
- public static class UriWallpaperInfo extends WallpaperTileInfo {
- private Uri mUri;
- public UriWallpaperInfo(Uri uri) {
- mUri = uri;
- }
- @Override
- public void onClick(WallpaperCropActivity a) {
- CropView v = a.getCropView();
- int rotation = WallpaperCropActivity.getRotationFromExif(a, mUri);
- v.setTileSource(new BitmapRegionTileSource(a, mUri, 1024, rotation), null);
- v.setTouchEnabled(true);
- }
- @Override
- public void onSave(final WallpaperCropActivity a) {
- boolean finishActivityWhenDone = true;
- OnBitmapCroppedHandler h = new OnBitmapCroppedHandler() {
- public void onBitmapCropped(byte[] imageBytes) {
- Point thumbSize = getDefaultThumbnailSize(a.getResources());
- Bitmap thumb = createThumbnail(
- thumbSize, null, null, imageBytes, null, 0, 0, true);
- a.getSavedImages().writeImage(thumb, imageBytes);
- }
- };
- ((LockWallpaperPickerActivity)a).cropImageAndSetWallpaper(mUri, h, finishActivityWhenDone);
- }
- @Override
- public boolean isSelectable() {
- return true;
- }
- @Override
- public boolean isNamelessWallpaper() {
- return true;
- }
- }
-
- /**
- * For themes which have regular wallpapers
- */
- public static class ThemeWallpaperInfo extends WallpaperTileInfo {
- String mPackageName;
- boolean mIsLegacy;
- Drawable mThumb;
- Context mContext;
-
- public ThemeWallpaperInfo(Context context, String packageName, boolean legacy, Drawable thumb) {
- this.mContext = context;
- this.mPackageName = packageName;
- this.mIsLegacy = legacy;
- this.mThumb = thumb;
- }
-
- @Override
- public void onClick(WallpaperCropActivity a) {
- CropView v = a.getCropView();
- try {
- BitmapRegionTileSource source = null;
- if (mIsLegacy) {
- final PackageManager pm = a.getPackageManager();
- PackageInfo pi = pm.getPackageInfo(mPackageName, 0);
- Resources res = a.getPackageManager().getResourcesForApplication(mPackageName);
- int resId = pi.legacyThemeInfos[0].wallpaperResourceId;
-
- int rotation = WallpaperCropActivity.getRotationFromExif(res, resId);
- source = new BitmapRegionTileSource(
- res, a, resId, 1024, rotation);
- } else {
- Resources res = a.getPackageManager().getResourcesForApplication(mPackageName);
- if (res == null) {
- return;
- }
-
- int rotation = 0;
- source = new BitmapRegionTileSource(
- res, a, "wallpapers", 1024, rotation, true);
- }
- v.setTileSource(source, null);
- v.setTouchEnabled(true);
- } catch (NameNotFoundException e) {
- }
- }
-
- @Override
- public void onSave(WallpaperCropActivity a) {
- ((LockWallpaperPickerActivity)a).cropImageAndSetWallpaper(
- "wallpapers",
- mPackageName,
- mIsLegacy,
- true);
- }
-
- @Override
- public boolean isNamelessWallpaper() {
- return true;
- }
-
- @Override
- public boolean isSelectable() {
- return true;
- }
- }
-
- /**
- * For themes that have LOCKSCREEN wallpapers
- */
- public static class ThemeLockWallpaperInfo extends WallpaperTileInfo {
- String mPackageName;
- Drawable mThumb;
- Context mContext;
-
- public ThemeLockWallpaperInfo(Context context, String packageName, Drawable thumb) {
- this.mContext = context;
- this.mPackageName = packageName;
- this.mThumb = thumb;
- }
-
- @Override
- public void onClick(WallpaperCropActivity a) {
- CropView v = a.getCropView();
- try {
- BitmapRegionTileSource source = null;
- Resources res = a.getPackageManager().getResourcesForApplication(mPackageName);
- if (res == null) {
- return;
- }
-
- int rotation = 0;
- source = new BitmapRegionTileSource(
- res, a, "lockscreen", 1024, rotation, true);
- v.setTileSource(source, null);
- v.setTouchEnabled(true);
- } catch (NameNotFoundException e) {
- }
- }
-
- @Override
- public void onSave(WallpaperCropActivity a) {
- ((LockWallpaperPickerActivity)a).cropImageAndSetWallpaper(
- "lockscreen",
- mPackageName,
- false,
- true);
- }
-
- @Override
- public boolean isNamelessWallpaper() {
- return true;
- }
-
- @Override
- public boolean isSelectable() {
- return true;
- }
- }
-
- public static class ResourceWallpaperInfo extends WallpaperTileInfo {
- private Resources mResources;
- private int mResId;
- private Drawable mThumb;
-
- public ResourceWallpaperInfo(Resources res, int resId, Drawable thumb) {
- mResources = res;
- mResId = resId;
- mThumb = thumb;
- }
- @Override
- public void onClick(WallpaperCropActivity a) {
- int rotation = WallpaperCropActivity.getRotationFromExif(mResources, mResId);
- BitmapRegionTileSource source = new BitmapRegionTileSource(
- mResources, a, mResId, 1024, rotation);
- CropView v = a.getCropView();
- v.setTileSource(source, null);
- Point wallpaperSize = WallpaperCropActivity.getDefaultWallpaperSize(
- a.getResources(), a.getWindowManager());
- RectF crop = WallpaperCropActivity.getMaxCropRect(
- source.getImageWidth(), source.getImageHeight(),
- wallpaperSize.x, wallpaperSize.y, false);
- v.setScale(wallpaperSize.x / crop.width());
- v.setTouchEnabled(false);
- }
- @Override
- public void onSave(WallpaperCropActivity a) {
- boolean finishActivityWhenDone = true;
- ((LockWallpaperPickerActivity)a).cropImageAndSetWallpaper(mResources, mResId, finishActivityWhenDone);
- }
- @Override
- public boolean isSelectable() {
- return true;
- }
- @Override
- public boolean isNamelessWallpaper() {
- return true;
- }
- }
-
- protected void cropImageAndSetWallpaper(String path, String packageName, final boolean legacy,
- final boolean finishActivityWhenDone) {
-
- Point outSize = new Point();
- getWindowManager().getDefaultDisplay().getSize(outSize);
-
- final int outWidth = outSize.x;
- final int outHeight = outSize.y;
- Runnable onEndCrop = new Runnable() {
- public void run() {
- if (finishActivityWhenDone) {
- setResult(Activity.RESULT_OK);
- finish();
- }
- }
- };
-
- RectF cropRect = new RectF(mCropView.getCrop());
- BitmapCropTask cropTask = null;
- try {
- if (legacy) {
- final PackageManager pm = getPackageManager();
- PackageInfo pi = pm.getPackageInfo(packageName, 0);
- Resources res = getPackageManager().getResourcesForApplication(packageName);
- int resId = pi.legacyThemeInfos[0].wallpaperResourceId;
- cropTask = new BitmapCropTask(this, res, resId,
- cropRect, 0, outWidth, outHeight, true, false, onEndCrop);
- } else {
- Resources res = getPackageManager().getResourcesForApplication(packageName);
- if (res == null) {
- return;
- }
- cropTask = new BitmapCropTask(this, res, path, cropRect,
- 0, outWidth, outHeight, true, false, onEndCrop);
- }
- } catch (NameNotFoundException e) {
- return;
- }
-
- if (cropTask != null) {
- cropTask.execute();
- }
- }
-
- @Override
- protected void cropImageAndSetWallpaper(Uri uri,
- OnBitmapCroppedHandler onBitmapCroppedHandler, final boolean finishActivityWhenDone) {
- // Get the crop
- boolean ltr = mCropView.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
-
- Point minDims = new Point();
- Point maxDims = new Point();
- Display d = getWindowManager().getDefaultDisplay();
- d.getCurrentSizeRange(minDims, maxDims);
-
- Point displaySize = new Point();
- d.getSize(displaySize);
-
- int maxDim = Math.max(maxDims.x, maxDims.y);
- final int minDim = Math.min(minDims.x, minDims.y);
- int defaultWallpaperWidth;
- if (isScreenLarge(getResources())) {
- defaultWallpaperWidth = (int) (maxDim *
- wallpaperTravelToScreenWidthRatio(maxDim, minDim));
- } else {
- defaultWallpaperWidth = Math.max((int)
- (minDim * WALLPAPER_SCREENS_SPAN), maxDim);
- }
-
- boolean isPortrait = displaySize.x < displaySize.y;
- int portraitHeight;
- if (isPortrait) {
- portraitHeight = mCropView.getHeight();
- } else {
- // TODO: how to actually get the proper portrait height?
- // This is not quite right:
- portraitHeight = Math.max(maxDims.x, maxDims.y);
- }
- if (android.os.Build.VERSION.SDK_INT >=
- android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
- Point realSize = new Point();
- d.getRealSize(realSize);
- portraitHeight = Math.max(realSize.x, realSize.y);
- }
- // Get the crop
- RectF cropRect = mCropView.getCrop();
- int cropRotation = mCropView.getImageRotation();
- float cropScale = mCropView.getWidth() / (float) cropRect.width();
-
- Point inSize = mCropView.getSourceDimensions();
- Matrix rotateMatrix = new Matrix();
- rotateMatrix.setRotate(cropRotation);
- float[] rotatedInSize = new float[] { inSize.x, inSize.y };
- rotateMatrix.mapPoints(rotatedInSize);
- rotatedInSize[0] = Math.abs(rotatedInSize[0]);
- rotatedInSize[1] = Math.abs(rotatedInSize[1]);
-
- // ADJUST CROP WIDTH
- // Extend the crop all the way to the right, for parallax
- // (or all the way to the left, in RTL)
- float extraSpace = ltr ? rotatedInSize[0] - cropRect.right : cropRect.left;
- // Cap the amount of extra width
- float maxExtraSpace = defaultWallpaperWidth / cropScale - cropRect.width();
- extraSpace = Math.min(extraSpace, maxExtraSpace);
-
- if (ltr) {
- cropRect.right += extraSpace;
- } else {
- cropRect.left -= extraSpace;
- }
-
- // ADJUST CROP HEIGHT
- if (isPortrait) {
- cropRect.bottom = cropRect.top + portraitHeight / cropScale;
- } else { // LANDSCAPE
- float extraPortraitHeight =
- portraitHeight / cropScale - cropRect.height();
- float expandHeight =
- Math.min(Math.min(rotatedInSize[1] - cropRect.bottom, cropRect.top),
- extraPortraitHeight / 2);
- cropRect.top -= expandHeight;
- cropRect.bottom += expandHeight;
- }
- final int outWidth = (int) Math.round(cropRect.width() * cropScale);
- final int outHeight = (int) Math.round(cropRect.height() * cropScale);
-
- Runnable onEndCrop = new Runnable() {
- public void run() {
- updateWallpaperDimensions(outWidth, outHeight);
- if (finishActivityWhenDone) {
- setResult(Activity.RESULT_OK);
- finish();
- }
- }
- };
- BitmapCropTask cropTask = new BitmapCropTask(this, uri,
- cropRect, cropRotation, outWidth, outHeight, true, false, onEndCrop);
- if (onBitmapCroppedHandler != null) {
- cropTask.setOnBitmapCropped(onBitmapCroppedHandler);
- }
- cropTask.execute();
- }
-
- @Override
- protected void setWallpaper(String filePath, final boolean finishActivityWhenDone) {
- int rotation = getRotationFromExif(filePath);
- BitmapCropTask cropTask = new BitmapCropTask(
- this, filePath, null, rotation, 0, 0, true, false, null);
- final Point bounds = cropTask.getImageBounds();
- Runnable onEndCrop = new Runnable() {
- public void run() {
- updateWallpaperDimensions(bounds.x, bounds.y);
- if (finishActivityWhenDone) {
- setResult(Activity.RESULT_OK);
- finish();
- }
- }
- };
- cropTask.setOnEndRunnable(onEndCrop);
- cropTask.setNoCrop(true);
- cropTask.execute();
- }
-
- protected static class BitmapCropTask extends AsyncTask<Void, Void, Boolean> {
- Uri mInUri = null;
- Context mContext;
- String mInFilePath;
- byte[] mInImageBytes;
- int mInResId = 0;
- InputStream mInStream;
- RectF mCropBounds = null;
- int mOutWidth, mOutHeight;
- int mRotation;
- String mOutputFormat = "jpg"; // for now
- boolean mSetWallpaper;
- boolean mSaveCroppedBitmap;
- Bitmap mCroppedBitmap;
- Runnable mOnEndRunnable;
- Resources mResources;
- OnBitmapCroppedHandler mOnBitmapCroppedHandler;
- boolean mNoCrop;
- boolean mImageFromAsset;
-
- public BitmapCropTask(Context c, Resources res , String assetPath,
- RectF cropBounds, int rotation, int outWidth, int outHeight,
- boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
- mContext = c;
- mResources = res;
- mInFilePath = assetPath;
- mImageFromAsset = true;
- init(cropBounds, rotation,
- outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
- }
-
- public BitmapCropTask(Context c, String filePath,
- RectF cropBounds, int rotation, int outWidth, int outHeight,
- boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
- mContext = c;
- mInFilePath = filePath;
- init(cropBounds, rotation,
- outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
- }
-
- public BitmapCropTask(byte[] imageBytes,
- RectF cropBounds, int rotation, int outWidth, int outHeight,
- boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
- mInImageBytes = imageBytes;
- init(cropBounds, rotation,
- outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
- }
-
- public BitmapCropTask(Context c, Uri inUri,
- RectF cropBounds, int rotation, int outWidth, int outHeight,
- boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
- mContext = c;
- mInUri = inUri;
- init(cropBounds, rotation,
- outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
- }
-
- public BitmapCropTask(Context c, Resources res, int inResId,
- RectF cropBounds, int rotation, int outWidth, int outHeight,
- boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
- mContext = c;
- mInResId = inResId;
- mResources = res;
- init(cropBounds, rotation,
- outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
- }
-
- private void init(RectF cropBounds, int rotation, int outWidth, int outHeight,
- boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
- mCropBounds = cropBounds;
- mRotation = rotation;
- mOutWidth = outWidth;
- mOutHeight = outHeight;
- mSetWallpaper = setWallpaper;
- mSaveCroppedBitmap = saveCroppedBitmap;
- mOnEndRunnable = onEndRunnable;
- }
-
- public void setOnBitmapCropped(OnBitmapCroppedHandler handler) {
- mOnBitmapCroppedHandler = handler;
- }
-
- public void setNoCrop(boolean value) {
- mNoCrop = value;
- }
-
- public void setOnEndRunnable(Runnable onEndRunnable) {
- mOnEndRunnable = onEndRunnable;
- }
-
- // Helper to setup input stream
- private void regenerateInputStream() {
- if (mInUri == null && mInResId == 0 && mInFilePath == null && mInImageBytes == null && !mImageFromAsset) {
- Log.w(TAG, "cannot read original file, no input URI, resource ID, or " +
- "image byte array given");
- } else {
- Utils.closeSilently(mInStream);
- try {
- if (mImageFromAsset) {
- AssetManager am = mResources.getAssets();
- String[] pathImages = am.list(mInFilePath);
- if (pathImages == null || pathImages.length == 0) {
- throw new IOException("did not find any images in path: " + mInFilePath);
- }
- InputStream is = am.open(mInFilePath + File.separator + pathImages[0]);
- mInStream = new BufferedInputStream(is);
- } else if (mInUri != null) {
- mInStream = new BufferedInputStream(
- mContext.getContentResolver().openInputStream(mInUri));
- } else if (mInFilePath != null) {
- mInStream = mContext.openFileInput(mInFilePath);
- } else if (mInImageBytes != null) {
- mInStream = new BufferedInputStream(
- new ByteArrayInputStream(mInImageBytes));
- } else {
- mInStream = new BufferedInputStream(
- mResources.openRawResource(mInResId));
- }
- } catch (FileNotFoundException e) {
- Log.w(TAG, "cannot read file: " + mInUri.toString(), e);
- } catch (IOException e) {
- Log.w(TAG, "cannot read file: " + mInUri.toString(), e);
- }
- }
- }
-
- public Point getImageBounds() {
- regenerateInputStream();
- if (mInStream != null) {
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inJustDecodeBounds = true;
- BitmapFactory.decodeStream(mInStream, null, options);
- if (options.outWidth != 0 && options.outHeight != 0) {
- return new Point(options.outWidth, options.outHeight);
- }
- }
- return null;
- }
-
- public void setCropBounds(RectF cropBounds) {
- mCropBounds = cropBounds;
- }
-
- public Bitmap getCroppedBitmap() {
- return mCroppedBitmap;
- }
- public boolean cropBitmap() {
- boolean failure = false;
-
- regenerateInputStream();
-
- WallpaperManager wallpaperManager = null;
- if (mSetWallpaper) {
- wallpaperManager = WallpaperManager.getInstance(mContext.getApplicationContext());
- }
- if (mSetWallpaper && mNoCrop && mInStream != null) {
- try {
- wallpaperManager.setKeyguardStream(mInStream);
- } catch (IOException e) {
- Log.w(TAG, "cannot write stream to wallpaper", e);
- failure = true;
- }
- return !failure;
- }
- if (mInStream != null) {
- // Find crop bounds (scaled to original image size)
- Rect roundedTrueCrop = new Rect();
- Matrix rotateMatrix = new Matrix();
- Matrix inverseRotateMatrix = new Matrix();
- if (mRotation > 0) {
- rotateMatrix.setRotate(mRotation);
- inverseRotateMatrix.setRotate(-mRotation);
-
- mCropBounds.roundOut(roundedTrueCrop);
- mCropBounds = new RectF(roundedTrueCrop);
-
- Point bounds = getImageBounds();
-
- float[] rotatedBounds = new float[] { bounds.x, bounds.y };
- rotateMatrix.mapPoints(rotatedBounds);
- rotatedBounds[0] = Math.abs(rotatedBounds[0]);
- rotatedBounds[1] = Math.abs(rotatedBounds[1]);
-
- mCropBounds.offset(-rotatedBounds[0]/2, -rotatedBounds[1]/2);
- inverseRotateMatrix.mapRect(mCropBounds);
- mCropBounds.offset(bounds.x/2, bounds.y/2);
-
- regenerateInputStream();
- }
-
- mCropBounds.roundOut(roundedTrueCrop);
-
- if (roundedTrueCrop.width() <= 0 || roundedTrueCrop.height() <= 0) {
- Log.w(TAG, "crop has bad values for full size image");
- failure = true;
- return false;
- }
-
- // See how much we're reducing the size of the image
- int scaleDownSampleSize = Math.min(roundedTrueCrop.width() / mOutWidth,
- roundedTrueCrop.height() / mOutHeight);
-
- // Attempt to open a region decoder
- BitmapRegionDecoder decoder = null;
- try {
- decoder = BitmapRegionDecoder.newInstance(mInStream, true);
- } catch (IOException e) {
- Log.w(TAG, "cannot open region decoder for file: " + mInUri.toString(), e);
- }
-
- Bitmap crop = null;
- if (decoder != null) {
- // Do region decoding to get crop bitmap
- BitmapFactory.Options options = new BitmapFactory.Options();
- if (scaleDownSampleSize > 1) {
- options.inSampleSize = scaleDownSampleSize;
- }
- crop = decoder.decodeRegion(roundedTrueCrop, options);
- decoder.recycle();
- }
-
- if (crop == null) {
- // BitmapRegionDecoder has failed, try to crop in-memory
- regenerateInputStream();
- Bitmap fullSize = null;
- if (mInStream != null) {
- BitmapFactory.Options options = new BitmapFactory.Options();
- if (scaleDownSampleSize > 1) {
- options.inSampleSize = scaleDownSampleSize;
- }
- fullSize = BitmapFactory.decodeStream(mInStream, null, options);
- }
- if (fullSize != null) {
- mCropBounds.left /= scaleDownSampleSize;
- mCropBounds.top /= scaleDownSampleSize;
- mCropBounds.bottom /= scaleDownSampleSize;
- mCropBounds.right /= scaleDownSampleSize;
- mCropBounds.roundOut(roundedTrueCrop);
-
- crop = Bitmap.createBitmap(fullSize, roundedTrueCrop.left,
- roundedTrueCrop.top, roundedTrueCrop.width(),
- roundedTrueCrop.height());
- }
- }
-
- if (crop == null) {
- Log.w(TAG, "cannot decode file: " + mInUri.toString());
- failure = true;
- return false;
- }
- if (mOutWidth > 0 && mOutHeight > 0 || mRotation > 0) {
- float[] dimsAfter = new float[] { crop.getWidth(), crop.getHeight() };
- rotateMatrix.mapPoints(dimsAfter);
- dimsAfter[0] = Math.abs(dimsAfter[0]);
- dimsAfter[1] = Math.abs(dimsAfter[1]);
-
- if (!(mOutWidth > 0 && mOutHeight > 0)) {
- mOutWidth = Math.round(dimsAfter[0]);
- mOutHeight = Math.round(dimsAfter[1]);
- }
-
- RectF cropRect = new RectF(0, 0, dimsAfter[0], dimsAfter[1]);
- RectF returnRect = new RectF(0, 0, mOutWidth, mOutHeight);
-
- Matrix m = new Matrix();
- if (mRotation == 0) {
- m.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL);
- } else {
- Matrix m1 = new Matrix();
- m1.setTranslate(-crop.getWidth() / 2f, -crop.getHeight() / 2f);
- Matrix m2 = new Matrix();
- m2.setRotate(mRotation);
- Matrix m3 = new Matrix();
- m3.setTranslate(dimsAfter[0] / 2f, dimsAfter[1] / 2f);
- Matrix m4 = new Matrix();
- m4.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL);
-
- Matrix c1 = new Matrix();
- c1.setConcat(m2, m1);
- Matrix c2 = new Matrix();
- c2.setConcat(m4, m3);
- m.setConcat(c2, c1);
- }
-
- Bitmap tmp = Bitmap.createBitmap((int) returnRect.width(),
- (int) returnRect.height(), Bitmap.Config.ARGB_8888);
- if (tmp != null) {
- Canvas c = new Canvas(tmp);
- Paint p = new Paint();
- p.setFilterBitmap(true);
- c.drawBitmap(crop, m, p);
- crop = tmp;
- }
- }
-
- if (mSaveCroppedBitmap) {
- mCroppedBitmap = crop;
- }
-
- // Get output compression format
- CompressFormat cf =
- convertExtensionToCompressFormat(getFileExtension(mOutputFormat));
-
- // Compress to byte array
- ByteArrayOutputStream tmpOut = new ByteArrayOutputStream(2048);
- if (crop.compress(cf, DEFAULT_COMPRESS_QUALITY, tmpOut)) {
- // If we need to set to the wallpaper, set it
- if (mSetWallpaper && wallpaperManager != null) {
- try {
- byte[] outByteArray = tmpOut.toByteArray();
- wallpaperManager.setKeyguardStream(new ByteArrayInputStream(outByteArray));
- if (mOnBitmapCroppedHandler != null) {
- mOnBitmapCroppedHandler.onBitmapCropped(outByteArray);
- }
- } catch (IOException e) {
- Log.w(TAG, "cannot write stream to wallpaper", e);
- failure = true;
- }
- }
- } else {
- Log.w(TAG, "cannot compress bitmap");
- failure = true;
- }
- } else {
- Log.w(TAG, "could not complete crop task because input stream is null");
- }
- return !failure; // True if any of the operations failed
- }
-
- @Override
- protected Boolean doInBackground(Void... params) {
- return cropBitmap();
- }
-
- @Override
- protected void onPostExecute(Boolean result) {
- if (mOnEndRunnable != null) {
- mOnEndRunnable.run();
- }
- }
- }
-
- @Override
- protected void setWallpaperStripYOffset(int offset) {
- mWallpaperStrip.setPadding(0, 0, 0, offset);
- }
-
- // called by onCreate; this is subclassed to overwrite WallpaperCropActivity
- protected void init() {
- setContentView(R.layout.wallpaper_picker);
-
- mCropView = (CropView) findViewById(R.id.cropView);
- mWallpaperStrip = findViewById(R.id.wallpaper_strip);
- mCropView.setTouchCallback(new CropView.TouchCallback() {
- LauncherViewPropertyAnimator mAnim;
- @Override
- public void onTouchDown() {
- if (mAnim != null) {
- mAnim.cancel();
- }
- if (mWallpaperStrip.getAlpha() == 1f) {
- mIgnoreNextTap = true;
- }
- mAnim = new LauncherViewPropertyAnimator(mWallpaperStrip);
- mAnim.alpha(0f)
- .setDuration(150)
- .addListener(new Animator.AnimatorListener() {
- public void onAnimationStart(Animator animator) { }
- public void onAnimationEnd(Animator animator) {
- mWallpaperStrip.setVisibility(View.INVISIBLE);
- }
- public void onAnimationCancel(Animator animator) { }
- public void onAnimationRepeat(Animator animator) { }
- });
- mAnim.setInterpolator(new AccelerateInterpolator(0.75f));
- mAnim.start();
- }
- @Override
- public void onTouchUp() {
- mIgnoreNextTap = false;
- }
- @Override
- public void onTap() {
- boolean ignoreTap = mIgnoreNextTap;
- mIgnoreNextTap = false;
- if (!ignoreTap) {
- if (mAnim != null) {
- mAnim.cancel();
- }
- mWallpaperStrip.setVisibility(View.VISIBLE);
- mAnim = new LauncherViewPropertyAnimator(mWallpaperStrip);
- mAnim.alpha(1f)
- .setDuration(150)
- .setInterpolator(new DecelerateInterpolator(0.75f));
- mAnim.start();
- }
- }
- });
-
- mThumbnailOnClickListener = new OnClickListener() {
- public void onClick(View v) {
- if (mActionMode != null) {
- // When CAB is up, clicking toggles the item instead
- if (v.isLongClickable()) {
- mLongClickListener.onLongClick(v);
- }
- return;
- }
- WallpaperTileInfo info = (WallpaperTileInfo) v.getTag();
- if (info.isSelectable()) {
- if (mSelectedThumb != null) {
- mSelectedThumb.setSelected(false);
- mSelectedThumb = null;
- }
- mSelectedThumb = v;
- v.setSelected(true);
- // TODO: Remove this once the accessibility framework and
- // services have better support for selection state.
- v.announceForAccessibility(
- getString(R.string.announce_selection, v.getContentDescription()));
- }
- info.onClick(LockWallpaperPickerActivity.this);
- }
- };
- mLongClickListener = new View.OnLongClickListener() {
- // Called when the user long-clicks on someView
- public boolean onLongClick(View view) {
- CheckableFrameLayout c = (CheckableFrameLayout) view;
- c.toggle();
-
- if (mActionMode != null) {
- mActionMode.invalidate();
- } else {
- // Start the CAB using the ActionMode.Callback defined below
- mActionMode = startActionMode(mActionModeCallback);
- int childCount = mWallpapersView.getChildCount();
- for (int i = 0; i < childCount; i++) {
- mWallpapersView.getChildAt(i).setSelected(false);
- }
- }
- return true;
- }
- };
-
- mWallpapersView = (LinearLayout) findViewById(R.id.wallpaper_list);
-
- // Add a tile for the Gallery
- LinearLayout masterWallpaperList = (LinearLayout) findViewById(R.id.master_wallpaper_list);
- FrameLayout pickImageTile = (FrameLayout) getLayoutInflater().
- inflate(R.layout.wallpaper_picker_image_picker_item, masterWallpaperList, false);
- setWallpaperItemPaddingToZero(pickImageTile);
- masterWallpaperList.addView(pickImageTile, 0);
-
- // Add tile for clear image
- FrameLayout clearImageTile = (FrameLayout) getLayoutInflater().
- inflate(R.layout.wallpaper_picker_clear, masterWallpaperList, false);
- setWallpaperItemPaddingToZero(clearImageTile);
- masterWallpaperList.addView(clearImageTile, 0);
-
- // theme LOCKSCREEN wallpapers
- ArrayList<ThemeLockWallpaperInfo> themeLockWallpapers = findThemeLockWallpapers();
- ThemeLockWallpapersAdapter tla = new ThemeLockWallpapersAdapter(this, themeLockWallpapers);
- populateWallpapersFromAdapter(mWallpapersView, tla, false, true);
-
- // theme wallpapers
- ArrayList<ThemeWallpaperInfo> themeWallpapers = findThemeWallpapers();
- ThemeWallpapersAdapter ta = new ThemeWallpapersAdapter(this, themeWallpapers);
- populateWallpapersFromAdapter(mWallpapersView, ta, false, true);
-
- // Populate the saved wallpapers
- mSavedImages = new SavedWallpaperImages(this);
- mSavedImages.loadThumbnailsAndImageIdList();
- populateWallpapersFromAdapter(mWallpapersView, mSavedImages, true, true);
-
- // Make its background the last photo taken on external storage
- Bitmap lastPhoto = getThumbnailOfLastPhoto();
- if (lastPhoto != null) {
- ImageView galleryThumbnailBg =
- (ImageView) pickImageTile.findViewById(R.id.wallpaper_image);
- galleryThumbnailBg.setImageBitmap(getThumbnailOfLastPhoto());
- int colorOverlay = getResources().getColor(R.color.wallpaper_picker_translucent_gray);
- galleryThumbnailBg.setColorFilter(colorOverlay, PorterDuff.Mode.SRC_ATOP);
- }
-
- PickImageInfo pickImageInfo = new PickImageInfo();
- pickImageTile.setTag(pickImageInfo);
- pickImageInfo.setView(pickImageTile);
- pickImageTile.setOnClickListener(mThumbnailOnClickListener);
- pickImageInfo.setView(pickImageTile);
-
- UserDesktopWallpaperInfo clearImageInfo = new UserDesktopWallpaperInfo();
- clearImageTile.setTag(clearImageInfo);
- clearImageInfo.setView(clearImageTile);
- clearImageTile.setOnClickListener(mThumbnailOnClickListener);
- clearImageInfo.setView(clearImageTile);
-
- updateTileIndices();
-
- // Update the scroll for RTL
- initializeScrollForRtl();
-
- // Create smooth layout transitions for when items are deleted
- final LayoutTransition transitioner = new LayoutTransition();
- transitioner.setDuration(200);
- transitioner.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, 0);
- transitioner.setAnimator(LayoutTransition.DISAPPEARING, null);
- mWallpapersView.setLayoutTransition(transitioner);
-
- // Action bar
- // Show the custom action bar view
- final ActionBar actionBar = getActionBar();
- actionBar.setCustomView(R.layout.actionbar_set_wallpaper);
- actionBar.getCustomView().setOnClickListener(
- new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- if (mSelectedThumb != null) {
- WallpaperTileInfo info = (WallpaperTileInfo) mSelectedThumb.getTag();
- info.onSave(LockWallpaperPickerActivity.this);
- }
- }
- });
-
- // CAB for deleting items
- mActionModeCallback = new ActionMode.Callback() {
- // Called when the action mode is created; startActionMode() was called
- @Override
- public boolean onCreateActionMode(ActionMode mode, Menu menu) {
- // Inflate a menu resource providing context menu items
- MenuInflater inflater = mode.getMenuInflater();
- inflater.inflate(R.menu.cab_delete_wallpapers, menu);
- return true;
- }
-
- private int numCheckedItems() {
- int childCount = mWallpapersView.getChildCount();
- int numCheckedItems = 0;
- for (int i = 0; i < childCount; i++) {
- CheckableFrameLayout c = (CheckableFrameLayout) mWallpapersView.getChildAt(i);
- if (c.isChecked()) {
- numCheckedItems++;
- }
- }
- return numCheckedItems;
- }
-
- // Called each time the action mode is shown. Always called after onCreateActionMode,
- // but may be called multiple times if the mode is invalidated.
- @Override
- public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
- int numCheckedItems = numCheckedItems();
- if (numCheckedItems == 0) {
- mode.finish();
- return true;
- } else {
- mode.setTitle(getResources().getQuantityString(
- R.plurals.number_of_items_selected, numCheckedItems, numCheckedItems));
- return true;
- }
- }
-
- // Called when the user selects a contextual menu item
- @Override
- public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
- int itemId = item.getItemId();
- if (itemId == R.id.menu_delete) {
- int childCount = mWallpapersView.getChildCount();
- ArrayList<View> viewsToRemove = new ArrayList<View>();
- for (int i = 0; i < childCount; i++) {
- CheckableFrameLayout c =
- (CheckableFrameLayout) mWallpapersView.getChildAt(i);
- if (c.isChecked()) {
- WallpaperTileInfo info = (WallpaperTileInfo) c.getTag();
- info.onDelete(LockWallpaperPickerActivity.this);
- viewsToRemove.add(c);
- }
- }
- for (View v : viewsToRemove) {
- mWallpapersView.removeView(v);
- }
- updateTileIndices();
- mode.finish(); // Action picked, so close the CAB
- return true;
- } else {
- return false;
- }
- }
-
- // Called when the user exits the action mode
- @Override
- public void onDestroyActionMode(ActionMode mode) {
- int childCount = mWallpapersView.getChildCount();
- for (int i = 0; i < childCount; i++) {
- CheckableFrameLayout c = (CheckableFrameLayout) mWallpapersView.getChildAt(i);
- c.setChecked(false);
- }
- mSelectedThumb.setSelected(true);
- mActionMode = null;
- }
- };
- }
-
- private void initializeScrollForRtl() {
- final HorizontalScrollView scroll =
- (HorizontalScrollView) findViewById(R.id.wallpaper_scroll_container);
-
- if (scroll.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
- final ViewTreeObserver observer = scroll.getViewTreeObserver();
- observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
- public void onGlobalLayout() {
- LinearLayout masterWallpaperList =
- (LinearLayout) findViewById(R.id.master_wallpaper_list);
- scroll.scrollTo(masterWallpaperList.getWidth(), 0);
- scroll.getViewTreeObserver().removeOnGlobalLayoutListener(this);
- }
- });
- }
- }
-
- public boolean enableRotation() {
- return super.enableRotation() || Launcher.sForceEnableRotation;
- }
-
- protected Bitmap getThumbnailOfLastPhoto() {
- Cursor cursor = MediaStore.Images.Media.query(getContentResolver(),
- MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
- new String[] { MediaStore.Images.ImageColumns._ID,
- MediaStore.Images.ImageColumns.DATE_TAKEN},
- null, null, MediaStore.Images.ImageColumns.DATE_TAKEN + " DESC LIMIT 1");
- Bitmap thumb = null;
- if (cursor != null) {
- if (cursor.moveToFirst()) {
- int id = cursor.getInt(0);
- thumb = MediaStore.Images.Thumbnails.getThumbnail(getContentResolver(),
- id, MediaStore.Images.Thumbnails.MINI_KIND, null);
- }
- cursor.close();
- }
- return thumb;
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- return super.onCreateOptionsMenu(menu);
- }
-
- @Override
- public boolean onPrepareOptionsMenu(Menu menu) {
- return super.onPrepareOptionsMenu(menu);
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- // Handle presses on the action bar items
- switch (item.getItemId()) {
- default:
- return super.onOptionsItemSelected(item);
- }
- }
-
- protected void onStop() {
- super.onStop();
- mWallpaperStrip = findViewById(R.id.wallpaper_strip);
- if (mWallpaperStrip.getAlpha() < 1f) {
- mWallpaperStrip.setAlpha(1f);
- mWallpaperStrip.setVisibility(View.VISIBLE);
- }
- }
-
- @Override
- protected void onDestroy() {
- mWallpapersView.removeAllViews();
- super.onDestroy();
- }
-
- protected void onSaveInstanceState(Bundle outState) {
- outState.putParcelableArrayList(TEMP_WALLPAPER_TILES, mTempWallpaperTiles);
- }
-
- protected void onRestoreInstanceState(Bundle savedInstanceState) {
- ArrayList<Uri> uris = savedInstanceState.getParcelableArrayList(TEMP_WALLPAPER_TILES);
- for (Uri uri : uris) {
- addTemporaryWallpaperTile(uri);
- }
- }
-
- private void populateWallpapersFromAdapter(ViewGroup parent, BaseAdapter adapter,
- boolean addLongPressHandler, boolean selectFirstTile) {
- for (int i = 0; i < adapter.getCount(); i++) {
- FrameLayout thumbnail = (FrameLayout) adapter.getView(i, null, parent);
- parent.addView(thumbnail, i);
- WallpaperTileInfo info = (WallpaperTileInfo) adapter.getItem(i);
- thumbnail.setTag(info);
- info.setView(thumbnail);
- if (addLongPressHandler) {
- addLongPressHandler(thumbnail);
- }
- thumbnail.setOnClickListener(mThumbnailOnClickListener);
- if (i == 0 && selectFirstTile) {
- mThumbnailOnClickListener.onClick(thumbnail);
- }
- }
- }
-
- private void updateTileIndices() {
- LinearLayout masterWallpaperList = (LinearLayout) findViewById(R.id.master_wallpaper_list);
- final int childCount = masterWallpaperList.getChildCount();
- final Resources res = getResources();
-
- // Do two passes; the first pass gets the total number of tiles
- int numTiles = 0;
- for (int passNum = 0; passNum < 2; passNum++) {
- int tileIndex = 0;
- for (int i = 0; i < childCount; i++) {
- View child = masterWallpaperList.getChildAt(i);
- LinearLayout subList;
-
- int subListStart;
- int subListEnd;
- if (child.getTag() instanceof WallpaperTileInfo) {
- subList = masterWallpaperList;
- subListStart = i;
- subListEnd = i + 1;
- } else { // if (child instanceof LinearLayout) {
- subList = (LinearLayout) child;
- subListStart = 0;
- subListEnd = subList.getChildCount();
- }
-
- for (int j = subListStart; j < subListEnd; j++) {
- WallpaperTileInfo info = (WallpaperTileInfo) subList.getChildAt(j).getTag();
- if (info.isNamelessWallpaper()) {
- if (passNum == 0) {
- numTiles++;
- } else {
- CharSequence label = res.getString(
- R.string.wallpaper_accessibility_name, ++tileIndex, numTiles);
- info.onIndexUpdated(label);
- }
- }
- }
- }
- }
- }
-
- private static Point getDefaultThumbnailSize(Resources res) {
- return new Point(res.getDimensionPixelSize(R.dimen.wallpaperThumbnailWidth),
- res.getDimensionPixelSize(R.dimen.wallpaperThumbnailHeight));
- }
-
- private static Bitmap createThumbnail(Point size, Context context, Uri uri, byte[] imageBytes,
- Resources res, int resId, int rotation, boolean leftAligned) {
- int width = size.x;
- int height = size.y;
-
- BitmapCropTask cropTask;
- if (uri != null) {
- cropTask = new BitmapCropTask(
- context, uri, null, rotation, width, height, false, true, null);
- } else if (imageBytes != null) {
- cropTask = new BitmapCropTask(
- imageBytes, null, rotation, width, height, false, true, null);
- } else {
- cropTask = new BitmapCropTask(
- context, res, resId, null, rotation, width, height, false, true, null);
- }
- Point bounds = cropTask.getImageBounds();
- if (bounds == null || bounds.x == 0 || bounds.y == 0) {
- return null;
- }
-
- Matrix rotateMatrix = new Matrix();
- rotateMatrix.setRotate(rotation);
- float[] rotatedBounds = new float[] { bounds.x, bounds.y };
- rotateMatrix.mapPoints(rotatedBounds);
- rotatedBounds[0] = Math.abs(rotatedBounds[0]);
- rotatedBounds[1] = Math.abs(rotatedBounds[1]);
-
- RectF cropRect = WallpaperCropActivity.getMaxCropRect(
- (int) rotatedBounds[0], (int) rotatedBounds[1], width, height, leftAligned);
- cropTask.setCropBounds(cropRect);
-
- if (cropTask.cropBitmap()) {
- return cropTask.getCroppedBitmap();
- } else {
- return null;
- }
- }
-
- private void addTemporaryWallpaperTile(Uri uri) {
- mTempWallpaperTiles.add(uri);
- // Add a tile for the image picked from Gallery
- FrameLayout pickedImageThumbnail = (FrameLayout) getLayoutInflater().
- inflate(R.layout.wallpaper_picker_item, mWallpapersView, false);
- setWallpaperItemPaddingToZero(pickedImageThumbnail);
-
- // Load the thumbnail
- ImageView image = (ImageView) pickedImageThumbnail.findViewById(R.id.wallpaper_image);
- Point defaultSize = getDefaultThumbnailSize(this.getResources());
- int rotation = WallpaperCropActivity.getRotationFromExif(this, uri);
- Bitmap thumb = createThumbnail(defaultSize, this, uri, null, null, 0, rotation, false);
- if (thumb != null) {
- image.setImageBitmap(thumb);
- Drawable thumbDrawable = image.getDrawable();
- thumbDrawable.setDither(true);
- } else {
- Log.e(TAG, "Error loading thumbnail for uri=" + uri);
- }
- mWallpapersView.addView(pickedImageThumbnail, 0);
-
- UriWallpaperInfo info = new UriWallpaperInfo(uri);
- pickedImageThumbnail.setTag(info);
- info.setView(pickedImageThumbnail);
- addLongPressHandler(pickedImageThumbnail);
- updateTileIndices();
- pickedImageThumbnail.setOnClickListener(mThumbnailOnClickListener);
- mThumbnailOnClickListener.onClick(pickedImageThumbnail);
- }
-
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (requestCode == IMAGE_PICK && resultCode == RESULT_OK) {
- if (data != null && data.getData() != null) {
- Uri uri = data.getData();
- addTemporaryWallpaperTile(uri);
- }
- } else if (requestCode == PICK_WALLPAPER_THIRD_PARTY_ACTIVITY) {
- setResult(RESULT_OK);
- finish();
- }
- }
-
- static void setWallpaperItemPaddingToZero(FrameLayout frameLayout) {
- frameLayout.setPadding(0, 0, 0, 0);
- frameLayout.setForeground(new ZeroPaddingDrawable(frameLayout.getForeground()));
- }
-
- private void addLongPressHandler(View v) {
- v.setOnLongClickListener(mLongClickListener);
- }
-
- private ArrayList<ThemeWallpaperInfo> findThemeWallpapers() {
- ArrayList<ThemeWallpaperInfo> themeWallpapers =
- new ArrayList<ThemeWallpaperInfo>();
- ContentResolver cr = getContentResolver();
- String[] projection = { ThemesColumns.PKG_NAME, ThemesColumns.IS_LEGACY_THEME };
- String selection = ThemesColumns.MODIFIES_LAUNCHER + "=? AND " +
- ThemesColumns.PKG_NAME + "!=?";
- String[] selectoinArgs = {"1", "default"};
- String sortOrder = null;
- Cursor c = cr.query(ThemesColumns.CONTENT_URI, projection, selection,
- selectoinArgs, sortOrder);
- if (c != null) {
- Bitmap bmp;
- while (c.moveToNext()) {
- String pkgName = c.getString(c.getColumnIndexOrThrow(ThemesColumns.PKG_NAME));
- boolean isLegacy = c.getInt(c.getColumnIndexOrThrow(
- ThemesColumns.IS_LEGACY_THEME)) == 1;
- bmp = getThemeWallpaper(this, "wallpapers", pkgName, isLegacy, true /* thumb*/);
- themeWallpapers.add(
- new ThemeWallpaperInfo(this, pkgName, isLegacy,
- new BitmapDrawable(getResources(), bmp)));
- if (bmp != null) {
- Log.d("", String.format("Loaded bitmap of size %dx%d for %s",
- bmp.getWidth(), bmp.getHeight(), pkgName));
- }
- }
- c.close();
- }
- return themeWallpapers;
- }
-
- private ArrayList<ThemeLockWallpaperInfo> findThemeLockWallpapers() {
- ArrayList<ThemeLockWallpaperInfo> themeWallpapers =
- new ArrayList<ThemeLockWallpaperInfo>();
- ContentResolver cr = getContentResolver();
- String[] projection = { ThemesColumns.PKG_NAME };
- String selection = ThemesColumns.MODIFIES_LOCKSCREEN + "=? AND " +
- ThemesColumns.PKG_NAME + "!=?";
- String[] selectoinArgs = {"1", "default"};
- String sortOrder = null;
- Cursor c = cr.query(ThemesColumns.CONTENT_URI, projection, selection,
- selectoinArgs, sortOrder);
- if (c != null) {
- Bitmap bmp;
- while (c.moveToNext()) {
- String pkgName = c.getString(c.getColumnIndexOrThrow(ThemesColumns.PKG_NAME));
- bmp = getThemeWallpaper(this, "lockscreen", pkgName, false, true /* thumb*/);
- themeWallpapers.add(
- new ThemeLockWallpaperInfo(this, pkgName,
- new BitmapDrawable(getResources(), bmp)));
- if (bmp != null) {
- Log.d("", String.format("Loaded bitmap of size %dx%d for %s",
- bmp.getWidth(), bmp.getHeight(), pkgName));
- }
- }
- c.close();
- }
- return themeWallpapers;
- }
-
- public Pair<ApplicationInfo, Integer> getWallpaperArrayResourceId() {
- // Context.getPackageName() may return the "original" package name,
- // com.android.launcher3; Resources needs the real package name,
- // com.android.launcher3. So we ask Resources for what it thinks the
- // package name should be.
- final String packageName = getResources().getResourcePackageName(R.array.wallpapers);
- try {
- ApplicationInfo info = getPackageManager().getApplicationInfo(packageName, 0);
- return new Pair<ApplicationInfo, Integer>(info, R.array.wallpapers);
- } catch (PackageManager.NameNotFoundException e) {
- return null;
- }
- }
-
- public CropView getCropView() {
- return mCropView;
- }
-
- public SavedWallpaperImages getSavedImages() {
- return mSavedImages;
- }
-
- static class ZeroPaddingDrawable extends LevelListDrawable {
- public ZeroPaddingDrawable(Drawable d) {
- super();
- addLevel(0, 0, d);
- setLevel(0);
- }
-
- @Override
- public boolean getPadding(Rect padding) {
- padding.set(0, 0, 0, 0);
- return true;
- }
- }
-
- private static class ThemeLockWallpapersAdapter extends BaseAdapter implements ListAdapter {
- private LayoutInflater mLayoutInflater;
- private ArrayList<ThemeLockWallpaperInfo> mWallpapers;
-
- ThemeLockWallpapersAdapter(Activity activity, ArrayList<ThemeLockWallpaperInfo> wallpapers) {
- mLayoutInflater = activity.getLayoutInflater();
- mWallpapers = wallpapers;
- }
-
- public int getCount() {
- return mWallpapers.size();
- }
-
- public ThemeLockWallpaperInfo getItem(int position) {
- return mWallpapers.get(position);
- }
-
- public long getItemId(int position) {
- return position;
- }
-
- public View getView(int position, View convertView, ViewGroup parent) {
- Drawable thumb = mWallpapers.get(position).mThumb;
- if (thumb == null) {
- Log.e(TAG, "Error decoding thumbnail for wallpaper #" + position);
- }
- return createImageTileView(mLayoutInflater, position, convertView, parent, thumb);
- }
- }
-
- private static class ThemeWallpapersAdapter extends BaseAdapter implements ListAdapter {
- private LayoutInflater mLayoutInflater;
- private ArrayList<ThemeWallpaperInfo> mWallpapers;
-
- ThemeWallpapersAdapter(Activity activity, ArrayList<ThemeWallpaperInfo> wallpapers) {
- mLayoutInflater = activity.getLayoutInflater();
- mWallpapers = wallpapers;
- }
-
- public int getCount() {
- return mWallpapers.size();
- }
-
- public ThemeWallpaperInfo getItem(int position) {
- return mWallpapers.get(position);
- }
-
- public long getItemId(int position) {
- return position;
- }
-
- public View getView(int position, View convertView, ViewGroup parent) {
- Drawable thumb = mWallpapers.get(position).mThumb;
- if (thumb == null) {
- Log.e(TAG, "Error decoding thumbnail for wallpaper #" + position);
- }
- return createImageTileView(mLayoutInflater, position, convertView, parent, thumb);
- }
- }
-
- public static View createImageTileView(LayoutInflater layoutInflater, int position,
- View convertView, ViewGroup parent, Drawable thumb) {
- View view;
-
- if (convertView == null) {
- view = layoutInflater.inflate(R.layout.wallpaper_picker_item, parent, false);
- } else {
- view = convertView;
- }
-
- setWallpaperItemPaddingToZero((FrameLayout) view);
-
- ImageView image = (ImageView) view.findViewById(R.id.wallpaper_image);
-
- if (thumb != null) {
- image.setImageDrawable(thumb);
- thumb.setDither(true);
- }
-
- return view;
- }
-
- private static Bitmap getThemeWallpaper(Context context, String path, String pkgName,
- boolean legacyTheme, boolean thumb) {
- if (legacyTheme) {
- return getLegacyThemeWallpaper(context, pkgName, thumb);
- }
- InputStream is = null;
- try {
- Resources res = context.getPackageManager().getResourcesForApplication(pkgName);
- if (res == null) {
- return null;
- }
-
- AssetManager am = res.getAssets();
- String[] wallpapers = am.list(path);
- if (wallpapers == null || wallpapers.length == 0) {
- return null;
- }
- is = am.open(path + File.separator + wallpapers[0]);
-
- BitmapFactory.Options bounds = new BitmapFactory.Options();
- bounds.inJustDecodeBounds = true;
- BitmapFactory.decodeStream(is, null, bounds);
- if ((bounds.outWidth == -1) || (bounds.outHeight == -1))
- return null;
-
- int originalSize = (bounds.outHeight > bounds.outWidth) ? bounds.outHeight
- : bounds.outWidth;
- Point outSize;
-
- if (thumb) {
- outSize = getDefaultThumbnailSize(context.getResources());
- } else {
- outSize = getDefaultWallpaperSize(res, ((Activity) context).getWindowManager());
- }
- int thumbSampleSize = (outSize.y > outSize.x) ? outSize.y : outSize.x;
-
- BitmapFactory.Options opts = new BitmapFactory.Options();
- opts.inSampleSize = originalSize / thumbSampleSize;
- return BitmapFactory.decodeStream(is, null, opts);
- } catch (IOException e) {
- return null;
- } catch (NameNotFoundException e) {
- return null;
- } catch (OutOfMemoryError e) {
- return null;
- } finally {
- if (is != null) {
- try {
- is.close();
- } catch (IOException e) {
- }
- }
- }
- }
-
- private static Bitmap getLegacyThemeWallpaper(Context context, String pkgName, boolean thumb) {
- try {
- final PackageManager pm = context.getPackageManager();
- PackageInfo pi = pm.getPackageInfo(pkgName, 0);
- Resources res = context.getPackageManager().getResourcesForApplication(pkgName);
-
- if (pi == null || res == null) {
- return null;
- }
- int resId = pi.legacyThemeInfos[0].wallpaperResourceId;
-
- BitmapFactory.Options opts = new BitmapFactory.Options();
- opts.inJustDecodeBounds = true;
- BitmapFactory.decodeResource(res, resId, opts);
- if ((opts.outWidth == -1) || (opts.outHeight == -1))
- return null;
-
- int originalSize = (opts.outHeight > opts.outWidth) ? opts.outHeight
- : opts.outWidth;
- Point outSize;
- if (thumb) {
- outSize = getDefaultThumbnailSize(context.getResources());
- } else {
- outSize = getDefaultWallpaperSize(res, ((Activity) context).getWindowManager());
- }
- int thumbSampleSize = (outSize.y > outSize.x) ? outSize.y : outSize.x;
-
- opts.inJustDecodeBounds = false;
- opts.inSampleSize = originalSize / thumbSampleSize;
-
- return BitmapFactory.decodeResource(res, resId, opts);
- } catch (NameNotFoundException e) {
- return null;
- } catch (OutOfMemoryError e1) {
- return null;
- }
- }
-}
diff --git a/src/com/android/launcher3/OnAlarmListener.java b/src/com/android/launcher3/OnAlarmListener.java
new file mode 100644
index 000000000..b5ef83e83
--- /dev/null
+++ b/src/com/android/launcher3/OnAlarmListener.java
@@ -0,0 +1,5 @@
+package com.android.launcher3;
+
+public interface OnAlarmListener {
+ public void onAlarm(Alarm alarm);
+}
diff --git a/src/com/android/launcher3/OverviewSettingsPanel.java b/src/com/android/launcher3/OverviewSettingsPanel.java
new file mode 100644
index 000000000..4466c15de
--- /dev/null
+++ b/src/com/android/launcher3/OverviewSettingsPanel.java
@@ -0,0 +1,233 @@
+package com.android.launcher3;
+
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.graphics.drawable.AnimationDrawable;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.ImageView;
+import android.widget.ListView;
+import com.android.launcher3.list.PinnedHeaderListView;
+import com.android.launcher3.list.SettingsPinnedHeaderAdapter;
+
+public class OverviewSettingsPanel {
+ public static final String ANDROID_SETTINGS = "com.android.settings";
+ public static final String ANDROID_PROTECTED_APPS =
+ "com.android.settings.applications.ProtectedAppsActivity";
+ public static final String THEME_SETTINGS =
+ "com.android.settings.Settings$ThemeSettingsActivity";
+ public static final int HOME_SETTINGS_POSITION = 0;
+ public static final int DRAWER_SETTINGS_POSITION = 1;
+
+ private Launcher mLauncher;
+ private View mOverviewPanel;
+ private SettingsPinnedHeaderAdapter mSettingsAdapter;
+ private PinnedHeaderListView mListView;
+ private String[] mValues;
+
+ OverviewSettingsPanel(Launcher launcher, View overviewPanel) {
+ mLauncher = launcher;
+ mOverviewPanel = overviewPanel;
+ }
+
+ // One time initialization of the SettingsPinnedHeaderAdapter
+ public void initializeAdapter() {
+ // Settings pane Listview
+ mListView = (PinnedHeaderListView) mLauncher
+ .findViewById(R.id.settings_home_screen_listview);
+ mListView.setOverScrollMode(ListView.OVER_SCROLL_NEVER);
+ Resources res = mLauncher.getResources();
+ String[] headers = new String[] {
+ res.getString(R.string.home_screen_settings),
+ res.getString(R.string.drawer_settings),
+ res.getString(R.string.app_settings)};
+
+ String[] values;
+ if(mLauncher.isGelIntegrationSupported()) {
+ values = new String[]{
+ res.getString(R.string.home_screen_search_text),
+ res.getString(R.string.search_screen_left_text),
+ res.getString(R.string.scroll_effect_text),
+ res.getString(R.string.icon_labels),
+ res.getString(R.string.scrolling_wallpaper),
+ res.getString(R.string.grid_size_text)};
+ } else {
+ values = new String[]{
+ res.getString(R.string.home_screen_search_text),
+ res.getString(R.string.scroll_effect_text),
+ res.getString(R.string.icon_labels),
+ res.getString(R.string.scrolling_wallpaper),
+ res.getString(R.string.grid_size_text)};
+ }
+
+ mValues = values;
+
+ String[] valuesDrawer = new String[] {
+ res.getString(R.string.scroll_effect_text),
+ res.getString(R.string.drawer_sorting_text),
+ res.getString(R.string.icon_labels)};
+
+ String[] valuesApp = new String[] {
+ res.getString(R.string.larger_icons_text),
+ res.getString(R.string.protected_app_settings)};
+
+
+ mSettingsAdapter = new SettingsPinnedHeaderAdapter(mLauncher);
+ mSettingsAdapter.setHeaders(headers);
+ mSettingsAdapter.addPartition(false, true);
+ mSettingsAdapter.addPartition(false, true);
+ mSettingsAdapter.addPartition(false, true);
+ mSettingsAdapter.mPinnedHeaderCount = headers.length;
+
+ mSettingsAdapter.changeCursor(0, createCursor(headers[0], values));
+ mSettingsAdapter.changeCursor(1, createCursor(headers[1], valuesDrawer));
+ mSettingsAdapter.changeCursor(2, createCursor(headers[2], valuesApp));
+ mListView.setAdapter(mSettingsAdapter);
+ }
+
+ private Cursor createCursor(String header, String[] values) {
+ MatrixCursor cursor = new MatrixCursor(new String[]{"_id", header});
+ int count = values.length;
+ for (int i = 0; i < count; i++) {
+ cursor.addRow(new Object[]{i, values[i]});
+ }
+ return cursor;
+ }
+
+ // One time View setup
+ public void initializeViews() {
+ mOverviewPanel.setAlpha(0f);
+ mOverviewPanel
+ .setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
+ ((SlidingUpPanelLayout) mOverviewPanel)
+ .setPanelSlideListener(new SettingsSimplePanelSlideListener());
+
+ //Quick Settings Buttons
+ View widgetButton = mLauncher.findViewById(R.id.widget_button);
+ widgetButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View arg0) {
+ if (!mLauncher.getWorkspace().isSwitchingState()) {
+ mLauncher.showAllApps(true, AppsCustomizePagedView.ContentType.Widgets, true);
+ }
+ }
+ });
+ widgetButton.setOnTouchListener(mLauncher.getHapticFeedbackTouchListener());
+
+ View wallpaperButton = mLauncher.findViewById(R.id.wallpaper_button);
+ wallpaperButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View arg0) {
+ if (!mLauncher.getWorkspace().isSwitchingState()) {
+ mLauncher.startWallpaper();
+ }
+ }
+ });
+ wallpaperButton.setOnTouchListener(mLauncher.getHapticFeedbackTouchListener());
+
+ View themesButton = mLauncher.findViewById(R.id.themes_button);
+ themesButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View arg0) {
+ if (!mLauncher.getWorkspace().isSwitchingState()) {
+ mLauncher.startThemeSettings();
+ }
+ }
+ });
+ themesButton.setOnTouchListener(mLauncher.getHapticFeedbackTouchListener());
+
+ View defaultScreenButton = mLauncher.findViewById(R.id.default_screen_button);
+ defaultScreenButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View arg0) {
+ if (!mLauncher.getWorkspace().isSwitchingState()) {
+ mLauncher.getWorkspace().onClickDefaultScreenButton();
+ }
+ }
+ });
+
+ defaultScreenButton.setOnTouchListener(mLauncher.getHapticFeedbackTouchListener());
+
+ //Handle
+ View v = mOverviewPanel.findViewById(R.id.settings_pane_header);
+ ((SlidingUpPanelLayout) mOverviewPanel).setEnableDragViewTouchEvents(true);
+ ((SlidingUpPanelLayout) mOverviewPanel).setDragView(v);
+ v.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (((SlidingUpPanelLayout) mOverviewPanel).isExpanded()) {
+ ((SlidingUpPanelLayout) mOverviewPanel).collapsePane();
+ } else {
+ ((SlidingUpPanelLayout) mOverviewPanel).expandPane();
+ }
+ }
+ });
+ }
+
+ public void update() {
+ Resources res = mLauncher.getResources();
+ View widgetButton = mOverviewPanel.findViewById(R.id.widget_button);
+ View wallpaperButton = mOverviewPanel
+ .findViewById(R.id.wallpaper_button);
+ View themesButton = mOverviewPanel.findViewById(R.id.themes_button);
+ View defaultHomePanel = mOverviewPanel.findViewById(R.id.default_screen_button);
+
+ boolean isAllAppsVisible = mLauncher.isAllAppsVisible();
+
+ PagedView pagedView = !isAllAppsVisible ? mLauncher.getWorkspace()
+ : mLauncher.getAppsCustomizeContent();
+
+ defaultHomePanel.setVisibility((pagedView.getPageCount() > 1) ?
+ View.VISIBLE : View.GONE);
+
+ if (mLauncher.isAllAppsVisible()) {
+ mSettingsAdapter.changeCursor(0, createCursor(res
+ .getString(R.string.home_screen_settings), new String[]{}));
+ } else {
+ mSettingsAdapter.changeCursor(0, createCursor(res
+ .getString(R.string.home_screen_settings), mValues));
+ }
+
+ // Make sure overview panel is drawn above apps customize and collapsed
+ mOverviewPanel.bringToFront();
+ mOverviewPanel.invalidate();
+
+ ((SlidingUpPanelLayout) mOverviewPanel).setPanelHeight(isAllAppsVisible ?
+ res.getDimensionPixelSize(R.dimen.settings_pane_handle)
+ : res.getDimensionPixelSize(R.dimen.sliding_panel_padding));
+
+ ((SlidingUpPanelLayout) mOverviewPanel).collapsePane();
+ }
+
+ public void notifyDataSetInvalidated() {
+ mSettingsAdapter.notifyDataSetInvalidated();
+ }
+
+
+ class SettingsSimplePanelSlideListener extends SlidingUpPanelLayout.SimplePanelSlideListener {
+ ImageView mAnimatedArrow;
+
+ public SettingsSimplePanelSlideListener() {
+ super();
+ mAnimatedArrow = (ImageView) mOverviewPanel.findViewById(R.id.settings_drag_arrow);
+ }
+
+ @Override
+ public void onPanelCollapsed(View panel) {
+ mAnimatedArrow.setBackgroundResource(R.drawable.transition_arrow_reverse);
+
+ AnimationDrawable frameAnimation = (AnimationDrawable) mAnimatedArrow.getBackground();
+ frameAnimation.start();
+ }
+
+ @Override
+ public void onPanelExpanded(View panel) {
+ mAnimatedArrow.setBackgroundResource(R.drawable.transition_arrow);
+
+ AnimationDrawable frameAnimation = (AnimationDrawable) mAnimatedArrow.getBackground();
+ frameAnimation.start();
+ }
+ }
+}
diff --git a/src/com/android/launcher3/PageIndicator.java b/src/com/android/launcher3/PageIndicator.java
index 08e5f721a..62ea03bcc 100644
--- a/src/com/android/launcher3/PageIndicator.java
+++ b/src/com/android/launcher3/PageIndicator.java
@@ -16,22 +16,12 @@
package com.android.launcher3;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
import android.animation.LayoutTransition;
-import android.animation.TimeInterpolator;
-import android.content.ComponentName;
import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
import android.content.res.TypedArray;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
import android.widget.LinearLayout;
-import com.android.launcher3.R;
import java.util.ArrayList;
diff --git a/src/com/android/launcher3/PageIndicatorMarker.java b/src/com/android/launcher3/PageIndicatorMarker.java
index b1025d6fe..f012db74b 100644
--- a/src/com/android/launcher3/PageIndicatorMarker.java
+++ b/src/com/android/launcher3/PageIndicatorMarker.java
@@ -16,17 +16,11 @@
package com.android.launcher3;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.LayoutTransition;
import android.content.Context;
import android.content.res.Resources;
import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
import android.widget.FrameLayout;
-import com.android.launcher3.R;
+import android.widget.ImageView;
public class PageIndicatorMarker extends FrameLayout {
@SuppressWarnings("unused")
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 6463e203d..7f145aea2 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -32,7 +32,6 @@ import android.graphics.Rect;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
-import android.support.v4.view.accessibility.AccessibilityEventCompat;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -47,8 +46,13 @@ import android.view.ViewParent;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.animation.*;
import android.widget.Scroller;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.AnimationUtils;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
import java.lang.reflect.Array;
import java.util.ArrayList;
@@ -120,8 +124,10 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
protected int mNextPage = INVALID_PAGE;
protected int mMaxScrollX;
- protected Scroller mScroller;
+ protected LauncherScroller mScroller;
+ private Interpolator mDefaultInterpolator;
private VelocityTracker mVelocityTracker;
+ private int mPageSpacing = 0;
private float mParentDownMotionX;
private float mParentDownMotionY;
@@ -150,12 +156,12 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
protected int mTouchState = TOUCH_STATE_REST;
protected boolean mForceScreenScrolled = false;
+
protected OnLongClickListener mLongClickListener;
protected int mTouchSlop;
private int mPagingTouchSlop;
private int mMaximumVelocity;
- protected int mPageSpacing;
protected int mPageLayoutPaddingTop;
protected int mPageLayoutPaddingBottom;
protected int mPageLayoutPaddingLeft;
@@ -255,20 +261,16 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
private int DELETE_SLIDE_IN_SIDE_PAGE_DURATION = 250;
private int DRAG_TO_DELETE_FADE_OUT_DURATION = 350;
+ protected boolean mEnforceRealBounds = false;
// Drop to delete
private View mDeleteDropTarget;
- private boolean mAutoComputePageSpacing = false;
- private boolean mRecomputePageSpacing = false;
-
// Bouncer
private boolean mTopAlignPageWhenShrinkingForBouncer = false;
protected final Rect mInsets = new Rect();
- protected int mFirstChildLeft;
-
- private Runnable mDelayedSnapToPageRunnable;
+ protected Runnable mDelayedSnapToPageRunnable;
// Relating to the scroll and overscroll effects
protected static float CAMERA_DISTANCE = 6500;
@@ -296,10 +298,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.PagedView, defStyle, 0);
- setPageSpacing(a.getDimensionPixelSize(R.styleable.PagedView_pageSpacing, 0));
- if (mPageSpacing < 0) {
- mAutoComputePageSpacing = mRecomputePageSpacing = true;
- }
+
mPageLayoutPaddingTop = a.getDimensionPixelSize(
R.styleable.PagedView_pageLayoutPaddingTop, 0);
mPageLayoutPaddingBottom = a.getDimensionPixelSize(
@@ -325,7 +324,8 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
protected void init() {
mDirtyPageContent = new ArrayList<Boolean>();
mDirtyPageContent.ensureCapacity(32);
- mScroller = new Scroller(getContext(), new ScrollInterpolator());
+ mScroller = new LauncherScroller(getContext());
+ setDefaultInterpolator(new ScrollInterpolator());
mCurrentPage = 0;
mCenterPagesVertically = true;
@@ -345,6 +345,11 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
setOnHierarchyChangeListener(this);
}
+ protected void setDefaultInterpolator(Interpolator interpolator) {
+ mDefaultInterpolator = interpolator;
+ mScroller.setInterpolator(mDefaultInterpolator);
+ }
+
protected void onAttachedToWindow() {
super.onAttachedToWindow();
@@ -460,6 +465,10 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
return new PageIndicator.PageMarkerResources();
}
+ /**
+ * Add a page change listener which will be called when a page is _finished_ listening.
+ *
+ */
public void setPageSwitchListener(PageSwitchListener pageSwitchListener) {
mPageSwitchListener = pageSwitchListener;
if (mPageSwitchListener != null) {
@@ -524,33 +533,42 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
}
scrollTo(newX, 0);
mScroller.setFinalX(newX);
- mScroller.forceFinished(true);
+ forceFinishScroller();
}
/**
* Called during AllApps/Home transitions to avoid unnecessary work. When that other animation
- * ends, {@link #resumeScrolling()} should be called, along with
- * {@link #updateCurrentPageScroll()} to correctly set the final state and re-enable scrolling.
+ * {@link #updateCurrentPageScroll()} should be called, to correctly set the final state and
+ * re-enable scrolling.
*/
- void pauseScrolling() {
- mScroller.forceFinished(true);
+ void stopScrolling() {
+ mCurrentPage = getNextPage();
+ notifyPageSwitchListener();
+ forceFinishScroller();
}
- /**
- * Enables scrolling again.
- * @see #pauseScrolling()
- */
- void resumeScrolling() {
+ private void abortScrollerAnimation(boolean resetNextPage) {
+ mScroller.abortAnimation();
+ // We need to clean up the next page here to avoid computeScrollHelper from
+ // updating current page on the pass.
+ if (resetNextPage) {
+ mNextPage = INVALID_PAGE;
+ }
}
+
+ private void forceFinishScroller() {
+ mScroller.forceFinished(true);
+ // We need to clean up the next page here to avoid computeScrollHelper from
+ // updating current page on the pass.
+ mNextPage = INVALID_PAGE;
+ }
+
/**
* Sets the current page.
*/
void setCurrentPage(int currentPage) {
if (!mScroller.isFinished()) {
- mScroller.abortAnimation();
- // We need to clean up the next page here to avoid computeScrollHelper from
- // updating current page on the pass.
- mNextPage = INVALID_PAGE;
+ abortScrollerAnimation(true);
}
// don't introduce any checks like mCurrentPage == currentPage here-- if we change the
// the default
@@ -571,12 +589,23 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
void setRestorePage(int restorePage) {
mRestorePage = restorePage;
}
+ int getRestorePage() {
+ return mRestorePage;
+ }
+ /**
+ * Should be called whenever the page changes. In the case of a scroll, we wait until the page
+ * has settled.
+ */
protected void notifyPageSwitchListener() {
if (mPageSwitchListener != null) {
- mPageSwitchListener.onPageSwitch(getPageAt(mCurrentPage), mCurrentPage);
+ mPageSwitchListener.onPageSwitch(getPageAt(getNextPage()), getNextPage());
}
+ updatePageIndicator();
+ }
+
+ private void updatePageIndicator() {
// Update the page indicator (when we aren't reordering)
if (mPageIndicator != null && !isReordering(false)) {
mPageIndicator.setActiveMarker(getNextPage());
@@ -635,7 +664,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
@Override
public void scrollTo(int x, int y) {
// In free scroll mode, we clamp the scrollX
- if (mFreeScroll) {
+ if (mFreeScroll || mEnforceRealBounds) {
x = Math.min(x, mFreeScrollMaxScrollX);
x = Math.max(x, mFreeScrollMinScrollX);
}
@@ -688,6 +717,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SCROLLED);
ev.setItemCount(getChildCount());
ev.setFromIndex(mCurrentPage);
+ ev.setToIndex(getNextPage());
final int action;
if (getNextPage() >= mCurrentPage) {
@@ -796,16 +826,16 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
- // NOTE: We multiply by 1.5f to account for the fact that depending on the offset of the
+ // NOTE: We multiply by 2f to account for the fact that depending on the offset of the
// viewport, we can be at most one and a half screens offset once we scale down
DisplayMetrics dm = getResources().getDisplayMetrics();
- int maxSize = Math.max(dm.widthPixels, dm.heightPixels + mInsets.top + mInsets.bottom);
+ int maxSize = Math.max(dm.widthPixels + mInsets.left + mInsets.right,
+ dm.heightPixels + mInsets.top + mInsets.bottom);
- int parentWidthSize, parentHeightSize;
+ int parentWidthSize = (int) (2f * maxSize);
+ int parentHeightSize = (int) (2f * maxSize);
int scaledWidthSize, scaledHeightSize;
if (mUseMinScale) {
- parentWidthSize = (int) (1.5f * maxSize);
- parentHeightSize = maxSize;
scaledWidthSize = (int) (parentWidthSize / mMinScale);
scaledHeightSize = (int) (parentHeightSize / mMinScale);
} else {
@@ -865,21 +895,17 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
childHeightMode = MeasureSpec.EXACTLY;
}
- childWidth = widthSize - horizontalPadding;
- childHeight = heightSize - verticalPadding - mInsets.top - mInsets.bottom;
+ childWidth = getViewportWidth() - horizontalPadding
+ - mInsets.left - mInsets.right;
+ childHeight = getViewportHeight() - verticalPadding
+ - mInsets.top - mInsets.bottom;
mNormalChildHeight = childHeight;
-
} else {
childWidthMode = MeasureSpec.EXACTLY;
childHeightMode = MeasureSpec.EXACTLY;
- if (mUseMinScale) {
- childWidth = getViewportWidth();
- childHeight = getViewportHeight();
- } else {
- childWidth = widthSize - getPaddingLeft() - getPaddingRight();
- childHeight = heightSize - getPaddingTop() - getPaddingBottom();
- }
+ childWidth = getViewportWidth() - mInsets.left - mInsets.right;
+ childHeight = getViewportHeight();
}
final int childWidthMeasureSpec =
@@ -890,30 +916,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
}
}
setMeasuredDimension(scaledWidthSize, scaledHeightSize);
-
- if (childCount > 0) {
- // Calculate the variable page spacing if necessary
- if (mAutoComputePageSpacing && mRecomputePageSpacing) {
- // The gap between pages in the PagedView should be equal to the gap from the page
- // to the edge of the screen (so it is not visible in the current screen). To
- // account for unequal padding on each side of the paged view, we take the maximum
- // of the left/right gap and use that as the gap between each page.
- int offset = (getViewportWidth() - getChildWidth(0)) / 2;
- int spacing = Math.max(offset, widthSize - offset -
- getChildAt(0).getMeasuredWidth());
- setPageSpacing(spacing);
- mRecomputePageSpacing = false;
- }
- }
- }
-
- public void setPageSpacing(int pageSpacing) {
- mPageSpacing = pageSpacing;
- requestLayout();
- }
-
- protected int getFirstChildLeft() {
- return mFirstChildLeft;
}
@Override
@@ -941,7 +943,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
int verticalPadding = getPaddingTop() + getPaddingBottom();
- int childLeft = mFirstChildLeft = offsetX + (screenWidth - getChildWidth(startIndex)) / 2;
+ int childLeft = offsetX + (screenWidth - getChildWidth(startIndex)) / 2;
if (mPageScrolls == null || getChildCount() != mChildCountOnLastLayout) {
mPageScrolls = new int[getChildCount()];
}
@@ -967,14 +969,12 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
child.layout(childLeft, childTop,
childLeft + child.getMeasuredWidth(), childTop + childHeight);
- // We assume the left and right padding are equal, and hence center the pages
- // horizontally
int scrollOffset = (getViewportWidth() - childWidth) / 2;
mPageScrolls[i] = childLeft - scrollOffset - offsetX;
if (i != endIndex - delta) {
childLeft += childWidth + scrollOffset;
- int nextScrollOffset = (getViewportWidth() - getChildWidth(i + delta)) / 2;
+ int nextScrollOffset = (getViewportWidth() - getChildWidth(i + delta)) /2;
childLeft += nextScrollOffset;
}
}
@@ -1044,6 +1044,11 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
mFadeInAdjacentScreens = fade;
}
+ public void setPageSpacing(int pageSpacing) {
+ mPageSpacing = pageSpacing;
+ requestLayout();
+ }
+
protected void screenScrolled(int screenCenter) {
boolean isInOverscroll = mOverScrollX < 0 || mOverScrollX > mMaxScrollX;
// Apply transition effect and adjacent screen fade if enabled
@@ -1136,7 +1141,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
// This ensures that when children are added, they get the correct transforms / alphas
// in accordance with any scroll effects.
mForceScreenScrolled = true;
- mRecomputePageSpacing = true;
updateFreescrollBounds();
invalidate();
}
@@ -1254,22 +1258,22 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
@Override
protected void dispatchDraw(Canvas canvas) {
- int halfScreenSize = getViewportWidth() / 2;
- // mOverScrollX is equal to getScrollX() when we're within the normal scroll range.
- // Otherwise it is equal to the scaled overscroll position.
- int screenCenter = mOverScrollX + halfScreenSize;
-
- if (screenCenter != mLastScreenCenter || mForceScreenScrolled) {
- // set mForceScreenScrolled before calling screenScrolled so that screenScrolled can
- // set it for the next frame
- mForceScreenScrolled = false;
- screenScrolled(screenCenter);
- mLastScreenCenter = screenCenter;
- }
-
// Find out which screens are visible; as an optimization we only call draw on them
final int pageCount = getChildCount();
if (pageCount > 0) {
+ int halfScreenSize = getViewportWidth() / 2;
+ // mOverScrollX is equal to getScrollX() when we're within the normal scroll range.
+ // Otherwise it is equal to the scaled overscroll position.
+ int screenCenter = mOverScrollX + halfScreenSize;
+
+ if (screenCenter != mLastScreenCenter || mForceScreenScrolled) {
+ // set mForceScreenScrolled before calling screenScrolled so that screenScrolled can
+ // set it for the next frame
+ mForceScreenScrolled = false;
+ screenScrolled(screenCenter);
+ mLastScreenCenter = screenCenter;
+ }
+
getVisiblePages(mTempVisiblePagesRange);
final int leftScreen = mTempVisiblePagesRange[0];
final int rightScreen = mTempVisiblePagesRange[1];
@@ -1405,24 +1409,22 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
* Return true if a tap at (x, y) should trigger a flip to the previous page.
*/
protected boolean hitsPreviousPage(float x, float y) {
- int offset = (getViewportWidth() - getChildWidth(mCurrentPage)) / 2;
if (isLayoutRtl()) {
return (x > (getViewportOffsetX() + getViewportWidth() -
- offset + mPageSpacing));
+ getPaddingRight() - mPageSpacing));
}
- return (x < getViewportOffsetX() + offset - mPageSpacing);
+ return (x < getViewportOffsetX() + getPaddingLeft() + mPageSpacing);
}
/**
* Return true if a tap at (x, y) should trigger a flip to the next page.
*/
protected boolean hitsNextPage(float x, float y) {
- int offset = (getViewportWidth() - getChildWidth(mCurrentPage)) / 2;
if (isLayoutRtl()) {
- return (x < getViewportOffsetX() + offset - mPageSpacing);
+ return (x < getViewportOffsetX() + getPaddingLeft() + mPageSpacing);
}
return (x > (getViewportOffsetX() + getViewportWidth() -
- offset + mPageSpacing));
+ getPaddingRight() - mPageSpacing));
}
/** Returns whether x and y originated within the buffered viewport */
@@ -1498,10 +1500,14 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
* being flinged.
*/
final int xDist = Math.abs(mScroller.getFinalX() - mScroller.getCurrX());
- final boolean finishedScrolling = (mScroller.isFinished() || xDist < mTouchSlop);
+ final boolean finishedScrolling = (mScroller.isFinished() || xDist < mTouchSlop / 3);
+
if (finishedScrolling) {
mTouchState = TOUCH_STATE_REST;
- mScroller.abortAnimation();
+ if (!mScroller.isFinished() && !mFreeScroll) {
+ setCurrentPage(getNextPage());
+ pageEndMoving();
+ }
} else {
if (isTouchPointInViewportWithBuffer((int) mDownMotionX, (int) mDownMotionY)) {
mTouchState = TOUCH_STATE_SCROLLING;
@@ -1613,8 +1619,21 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
protected float getScrollProgress(int screenCenter, View v, int page) {
final int halfScreenSize = getViewportWidth() / 2;
- int totalDistance = v.getMeasuredWidth() + mPageSpacing;
int delta = screenCenter - (getScrollForPage(page) + halfScreenSize);
+ int count = getChildCount();
+
+ final int totalDistance;
+
+ int adjacentPage = page + 1;
+ if ((delta < 0 && !isLayoutRtl()) || (delta > 0 && isLayoutRtl())) {
+ adjacentPage = page - 1;
+ }
+
+ if (adjacentPage < 0 || adjacentPage > count - 1) {
+ totalDistance = v.getMeasuredWidth() + mPageSpacing;
+ } else {
+ totalDistance = Math.abs(getScrollForPage(adjacentPage) - getScrollForPage(page));
+ }
float scrollProgress = delta / (totalDistance * 1.0f);
scrollProgress = Math.min(scrollProgress, getMaxScrollProgress());
@@ -1637,9 +1656,15 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
return 0;
} else {
View child = getChildAt(index);
- int scrollOffset = (getViewportWidth() - child.getMeasuredWidth()) / 2;
+
+ int scrollOffset = 0;
+ LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if (!lp.isFullScreenPage) {
+ scrollOffset = isLayoutRtl() ? getPaddingRight() : getPaddingLeft();
+ }
+
int baselineX = mPageScrolls[index] + scrollOffset + getViewportOffsetX();
- return (int) (child.getX() - baselineX);
+ return (int) (child.getLeft() - baselineX);
}
}
@@ -1712,11 +1737,11 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
}
protected void enableFreeScroll() {
- setEnableFreeScroll(true, -1);
+ setEnableFreeScroll(true);
}
- protected void disableFreeScroll(int snapPage) {
- setEnableFreeScroll(false, snapPage);
+ protected void disableFreeScroll() {
+ setEnableFreeScroll(false);
}
void updateFreescrollBounds() {
@@ -1730,16 +1755,10 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
}
}
- private void setEnableFreeScroll(boolean freeScroll, int snapPage) {
+ private void setEnableFreeScroll(boolean freeScroll) {
mFreeScroll = freeScroll;
- if (snapPage == -1) {
- snapPage = getPageNearestToCenterOfScreen();
- }
-
- if (!mFreeScroll) {
- snapToPage(snapPage);
- } else {
+ if (mFreeScroll) {
updateFreescrollBounds();
getOverviewModePages(mTempVisiblePagesRange);
if (getCurrentPage() < mTempVisiblePagesRange[0]) {
@@ -1799,7 +1818,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
* will be false if being flinged.
*/
if (!mScroller.isFinished()) {
- mScroller.abortAnimation();
+ abortScrollerAnimation(false);
}
// Remember where the motion event started
@@ -1925,7 +1944,9 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
addView(mDragView, pageUnderPointIndex);
onAddView(mDragView, pageUnderPointIndex);
mSidePageHoverIndex = -1;
- mPageIndicator.setActiveMarker(getNextPage());
+ if (mPageIndicator != null) {
+ mPageIndicator.setActiveMarker(getNextPage());
+ }
}
};
postDelayed(mSidePageHoverRunnable, REORDERING_SIDE_PAGE_HOVER_TIMEOUT);
@@ -1985,29 +2006,31 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
snapToPageWithVelocity(finalPage, velocityX);
} else {
snapToDestination();
- } } else if (mTouchState == TOUCH_STATE_PREV_PAGE) {
- // at this point we have not moved beyond the touch slop
- // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
- // we can just page
- int nextPage = Math.max(0, mCurrentPage - 1);
- if (nextPage != mCurrentPage) {
- snapToPage(nextPage);
- } else {
- snapToDestination();
}
} else {
if (!mScroller.isFinished()) {
- mScroller.abortAnimation();
+ abortScrollerAnimation(true);
}
float scaleX = getScaleX();
int vX = (int) (-velocityX * scaleX);
int initialScrollX = (int) (getScrollX() * scaleX);
+ mScroller.setInterpolator(mDefaultInterpolator);
mScroller.fling(initialScrollX,
getScrollY(), vX, 0, Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 0);
invalidate();
}
+ } else if (mTouchState == TOUCH_STATE_PREV_PAGE) {
+ // at this point we have not moved beyond the touch slop
+ // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
+ // we can just page
+ int nextPage = Math.max(0, mCurrentPage - 1);
+ if (nextPage != mCurrentPage) {
+ snapToPage(nextPage);
+ } else {
+ snapToDestination();
+ }
} else if (mTouchState == TOUCH_STATE_NEXT_PAGE) {
// at this point we have not moved beyond the touch slop
// (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
@@ -2267,27 +2290,33 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
}
protected void snapToPageImmediately(int whichPage) {
- snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION, true);
+ snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION, true, null);
}
protected void snapToPage(int whichPage, int duration) {
- snapToPage(whichPage, duration, false);
+ snapToPage(whichPage, duration, false, null);
}
- protected void snapToPage(int whichPage, int duration, boolean immediate) {
+ protected void snapToPage(int whichPage, int duration, TimeInterpolator interpolator) {
+ snapToPage(whichPage, duration, false, interpolator);
+ }
+
+ protected void snapToPage(int whichPage, int duration, boolean immediate,
+ TimeInterpolator interpolator) {
whichPage = Math.max(0, Math.min(whichPage, getPageCount() - 1));
int newX = getScrollForPage(whichPage);
final int sX = mUnboundedScrollX;
final int delta = newX - sX;
- snapToPage(whichPage, delta, duration, immediate);
+ snapToPage(whichPage, delta, duration, immediate, interpolator);
}
protected void snapToPage(int whichPage, int delta, int duration) {
- snapToPage(whichPage, delta, duration, false);
+ snapToPage(whichPage, delta, duration, false, null);
}
- protected void snapToPage(int whichPage, int delta, int duration, boolean immediate) {
+ protected void snapToPage(int whichPage, int delta, int duration, boolean immediate,
+ TimeInterpolator interpolator) {
mNextPage = whichPage;
View focusedChild = getFocusedChild();
if (focusedChild != null && whichPage != mCurrentPage &&
@@ -2306,11 +2335,18 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
}
if (!mScroller.isFinished()) {
- mScroller.abortAnimation();
+ abortScrollerAnimation(false);
}
+
+ if (interpolator != null) {
+ mScroller.setInterpolator(interpolator);
+ } else {
+ mScroller.setInterpolator(mDefaultInterpolator);
+ }
+
mScroller.startScroll(mUnboundedScrollX, 0, delta, 0, duration);
- notifyPageSwitchListener();
+ updatePageIndicator();
// Trigger a compute() to finish switching pages if necessary
if (immediate) {
@@ -2469,8 +2505,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
if (mContentIsRefreshable) {
// Force all scrolling-related behavior to end
- mScroller.forceFinished(true);
- mNextPage = INVALID_PAGE;
+ forceFinishScroller();
// Update all the pages
syncPages();
@@ -2565,7 +2600,8 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
mDragView = getChildAt(dragViewIndex);
mDragView.animate().scaleX(1.15f).scaleY(1.15f).setDuration(100).start();
mDragViewBaselineLeft = mDragView.getLeft();
- disableFreeScroll(-1);
+ snapToPage(getPageNearestToCenterOfScreen());
+ disableFreeScroll();
onStartReordering();
return true;
}
@@ -2597,7 +2633,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
mPostReorderingPreZoomInRunnable = new Runnable() {
public void run() {
onCompleteRunnable.run();
- enableFreeScroll();
};
};
diff --git a/src/com/android/launcher3/PagedViewIcon.java b/src/com/android/launcher3/PagedViewIcon.java
index 785725852..01e41078b 100644
--- a/src/com/android/launcher3/PagedViewIcon.java
+++ b/src/com/android/launcher3/PagedViewIcon.java
@@ -20,6 +20,7 @@ import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Region;
+import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;
@@ -64,17 +65,22 @@ public class PagedViewIcon extends TextView {
// Ensure we are using the right text size
LauncherAppState app = LauncherAppState.getInstance();
DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
- setTextSize(TypedValue.COMPLEX_UNIT_SP, grid.iconTextSize);
+ setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.allAppsIconTextSizePx);
mTextColor = getCurrentTextColor();
mIsTextVisible = mTextColor != getResources().getColor(android.R.color.transparent);
}
public void applyFromApplicationInfo(AppInfo info, boolean scaleUp,
PagedViewIcon.PressedCallback cb) {
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+
mIcon = info.iconBitmap;
mPressedCallback = cb;
- setCompoundDrawables(null, Utilities.createIconDrawable(mIcon),
- null, null);
+ Drawable icon = Utilities.createIconDrawable(mIcon);
+ icon.setBounds(0, 0, grid.allAppsIconSizePx, grid.allAppsIconSizePx);
+ setCompoundDrawables(null, icon, null, null);
+ setCompoundDrawablePadding(grid.iconDrawablePaddingPx);
setText(info.title);
setTag(info);
}
diff --git a/src/com/android/launcher3/PagedViewIconCache.java b/src/com/android/launcher3/PagedViewIconCache.java
index 8d8924b7e..93887ea23 100644
--- a/src/com/android/launcher3/PagedViewIconCache.java
+++ b/src/com/android/launcher3/PagedViewIconCache.java
@@ -16,17 +16,17 @@
package com.android.launcher3;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.pm.ComponentInfo;
import android.content.pm.ResolveInfo;
import android.graphics.Bitmap;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+
/**
* Simple cache mechanism for PagedView outlines.
*/
diff --git a/src/com/android/launcher3/PagedViewWidget.java b/src/com/android/launcher3/PagedViewWidget.java
index 45320a484..db4aeb940 100644
--- a/src/com/android/launcher3/PagedViewWidget.java
+++ b/src/com/android/launcher3/PagedViewWidget.java
@@ -30,8 +30,6 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
-import com.android.launcher3.R;
-
/**
* The linear layout used strictly for the widget/wallpaper tab of the customization tray
*/
@@ -84,11 +82,11 @@ public class PagedViewWidget extends LinearLayout {
DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
TextView name = (TextView) findViewById(R.id.widget_name);
if (name != null) {
- name.setTextSize(TypedValue.COMPLEX_UNIT_SP, grid.iconTextSize);
+ name.setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.iconTextSizePx);
}
TextView dims = (TextView) findViewById(R.id.widget_dims);
if (dims != null) {
- dims.setTextSize(TypedValue.COMPLEX_UNIT_SP, grid.iconTextSize);
+ dims.setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.iconTextSizePx);
}
}
diff --git a/src/com/android/launcher3/PreloadReceiver.java b/src/com/android/launcher3/PreloadReceiver.java
index 75e5c98bc..ca25746eb 100644
--- a/src/com/android/launcher3/PreloadReceiver.java
+++ b/src/com/android/launcher3/PreloadReceiver.java
@@ -19,6 +19,7 @@ package com.android.launcher3;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.os.AsyncTask;
import android.text.TextUtils;
import android.util.Log;
@@ -39,12 +40,12 @@ public class PreloadReceiver extends BroadcastReceiver {
if (LOGD) {
Log.d(TAG, "workspace name: " + name + " id: " + workspaceResId);
}
- new Thread(new Runnable() {
- @Override
- public void run() {
+ new AsyncTask<Void, Void, Void>() {
+ public Void doInBackground(Void ... args) {
provider.loadDefaultFavoritesIfNecessary(workspaceResId);
+ return null;
}
- }).start();
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
}
}
}
diff --git a/src/com/android/launcher3/RampUpScroller.java b/src/com/android/launcher3/RampUpScroller.java
deleted file mode 100644
index 89eb5798e..000000000
--- a/src/com/android/launcher3/RampUpScroller.java
+++ /dev/null
@@ -1,120 +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.view.animation.AccelerateInterpolator;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
-
-/**
- * Scroller that gradually reaches a target velocity.
- */
-class RampUpScroller {
- private final Interpolator mInterpolator;
- private final long mRampUpTime;
-
- private long mStartTime;
- private long mDeltaTime;
- private float mTargetVelocityX;
- private float mTargetVelocityY;
- private int mDeltaX;
- private int mDeltaY;
-
- /**
- * Creates a new ramp-up scroller that reaches full velocity after a
- * specified duration.
- *
- * @param rampUpTime Duration before the scroller reaches target velocity.
- */
- public RampUpScroller(long rampUpTime) {
- mInterpolator = new AccelerateInterpolator();
- mRampUpTime = rampUpTime;
- }
-
- /**
- * Starts the scroller at the current animation time.
- */
- public void start() {
- mStartTime = AnimationUtils.currentAnimationTimeMillis();
- mDeltaTime = mStartTime;
- }
-
- /**
- * Computes the current scroll deltas. This usually only be called after
- * starting the scroller with {@link #start()}.
- *
- * @see #getDeltaX()
- * @see #getDeltaY()
- */
- public void computeScrollDelta() {
- final long currentTime = AnimationUtils.currentAnimationTimeMillis();
- final long elapsedSinceStart = currentTime - mStartTime;
- final float scale;
- if (elapsedSinceStart < mRampUpTime) {
- scale = mInterpolator.getInterpolation((float) elapsedSinceStart / mRampUpTime);
- } else {
- scale = 1f;
- }
-
- final long elapsedSinceDelta = currentTime - mDeltaTime;
- mDeltaTime = currentTime;
-
- mDeltaX = (int) (elapsedSinceDelta * scale * mTargetVelocityX);
- mDeltaY = (int) (elapsedSinceDelta * scale * mTargetVelocityY);
- }
-
- /**
- * Sets the target velocity for this scroller.
- *
- * @param x The target X velocity in pixels per millisecond.
- * @param y The target Y velocity in pixels per millisecond.
- */
- public void setTargetVelocity(float x, float y) {
- mTargetVelocityX = x;
- mTargetVelocityY = y;
- }
-
- /**
- * @return The target X velocity for this scroller.
- */
- public float getTargetVelocityX() {
- return mTargetVelocityX;
- }
-
- /**
- * @return The target Y velocity for this scroller.
- */
- public float getTargetVelocityY() {
- return mTargetVelocityY;
- }
-
- /**
- * The distance traveled in the X-coordinate computed by the last call to
- * {@link #computeScrollDelta()}.
- */
- public int getDeltaX() {
- return mDeltaX;
- }
-
- /**
- * The distance traveled in the Y-coordinate computed by the last call to
- * {@link #computeScrollDelta()}.
- */
- public int getDeltaY() {
- return mDeltaY;
- }
-}
diff --git a/src/com/android/launcher3/SavedWallpaperImages.java b/src/com/android/launcher3/SavedWallpaperImages.java
deleted file mode 100644
index a418b8f5c..000000000
--- a/src/com/android/launcher3/SavedWallpaperImages.java
+++ /dev/null
@@ -1,244 +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.app.Activity;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.util.Log;
-import android.util.Pair;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-import android.widget.ListAdapter;
-
-import com.android.photos.BitmapRegionTileSource;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-
-
-public class SavedWallpaperImages extends BaseAdapter implements ListAdapter {
- private static String TAG = "Launcher3.SavedWallpaperImages";
- private ImageDb mDb;
- ArrayList<SavedWallpaperTile> mImages;
- Context mContext;
- LayoutInflater mLayoutInflater;
-
- public static class SavedWallpaperTile extends WallpaperCropActivity.WallpaperTileInfo {
- private int mDbId;
- private Drawable mThumb;
- public SavedWallpaperTile(int dbId, Drawable thumb) {
- mDbId = dbId;
- mThumb = thumb;
- }
- @Override
- public void onClick(WallpaperCropActivity a) {
- String imageFilename = a.getSavedImages().getImageFilename(mDbId);
- File file = new File(a.getFilesDir(), imageFilename);
- CropView v = a.getCropView();
- int rotation = WallpaperCropActivity.getRotationFromExif(file.getAbsolutePath());
- v.setTileSource(
- new BitmapRegionTileSource(a, file.getAbsolutePath(), 1024, rotation), null);
- v.moveToLeft();
- v.setTouchEnabled(false);
- }
- @Override
- public void onSave(WallpaperCropActivity a) {
- boolean finishActivityWhenDone = true;
- String imageFilename = a.getSavedImages().getImageFilename(mDbId);
- a.setWallpaper(imageFilename, finishActivityWhenDone);
- }
- @Override
- public void onDelete(WallpaperCropActivity a) {
- a.getSavedImages().deleteImage(mDbId);
- }
- @Override
- public boolean isSelectable() {
- return true;
- }
- @Override
- public boolean isNamelessWallpaper() {
- return true;
- }
- }
-
- public SavedWallpaperImages(Activity context) {
- mDb = new ImageDb(context);
- mContext = context;
- mLayoutInflater = context.getLayoutInflater();
- }
-
- public void loadThumbnailsAndImageIdList() {
- mImages = new ArrayList<SavedWallpaperTile>();
- SQLiteDatabase db = mDb.getReadableDatabase();
- Cursor result = db.query(ImageDb.TABLE_NAME,
- new String[] { ImageDb.COLUMN_ID,
- ImageDb.COLUMN_IMAGE_THUMBNAIL_FILENAME }, // cols to return
- null, // select query
- null, // args to select query
- null,
- null,
- ImageDb.COLUMN_ID + " DESC",
- null);
-
- while (result.moveToNext()) {
- String filename = result.getString(1);
- File file = new File(mContext.getFilesDir(), filename);
-
- Bitmap thumb = BitmapFactory.decodeFile(file.getAbsolutePath());
- if (thumb != null) {
- mImages.add(new SavedWallpaperTile(result.getInt(0), new BitmapDrawable(thumb)));
- }
- }
- result.close();
- }
-
- public int getCount() {
- return mImages.size();
- }
-
- public SavedWallpaperTile getItem(int position) {
- return mImages.get(position);
- }
-
- public long getItemId(int position) {
- return position;
- }
-
- public View getView(int position, View convertView, ViewGroup parent) {
- Drawable thumbDrawable = mImages.get(position).mThumb;
- if (thumbDrawable == null) {
- Log.e(TAG, "Error decoding thumbnail for wallpaper #" + position);
- }
- return WallpaperPickerActivity.createImageTileView(
- mLayoutInflater, position, convertView, parent, thumbDrawable);
- }
-
- public String getImageFilename(int id) {
- Pair<String, String> filenames = getImageFilenames(id);
- if (filenames != null) {
- return filenames.second;
- }
- return null;
- }
-
- private Pair<String, String> getImageFilenames(int id) {
- SQLiteDatabase db = mDb.getReadableDatabase();
- Cursor result = db.query(ImageDb.TABLE_NAME,
- new String[] { ImageDb.COLUMN_IMAGE_THUMBNAIL_FILENAME,
- ImageDb.COLUMN_IMAGE_FILENAME }, // cols to return
- ImageDb.COLUMN_ID + " = ?", // select query
- new String[] { Integer.toString(id) }, // args to select query
- null,
- null,
- null,
- null);
- if (result.getCount() > 0) {
- result.moveToFirst();
- String thumbFilename = result.getString(0);
- String imageFilename = result.getString(1);
- result.close();
- return new Pair<String, String>(thumbFilename, imageFilename);
- } else {
- return null;
- }
- }
-
- public void deleteImage(int id) {
- Pair<String, String> filenames = getImageFilenames(id);
- File imageFile = new File(mContext.getFilesDir(), filenames.first);
- imageFile.delete();
- File thumbFile = new File(mContext.getFilesDir(), filenames.second);
- thumbFile.delete();
- SQLiteDatabase db = mDb.getWritableDatabase();
- db.delete(ImageDb.TABLE_NAME,
- ImageDb.COLUMN_ID + " = ?", // SELECT query
- new String[] {
- Integer.toString(id) // args to SELECT query
- });
- }
-
- public void writeImage(Bitmap thumbnail, byte[] imageBytes) {
- try {
- File imageFile = File.createTempFile("wallpaper", "", mContext.getFilesDir());
- FileOutputStream imageFileStream =
- mContext.openFileOutput(imageFile.getName(), Context.MODE_PRIVATE);
- imageFileStream.write(imageBytes);
- imageFileStream.close();
-
- File thumbFile = File.createTempFile("wallpaperthumb", "", mContext.getFilesDir());
- FileOutputStream thumbFileStream =
- mContext.openFileOutput(thumbFile.getName(), Context.MODE_PRIVATE);
- thumbnail.compress(Bitmap.CompressFormat.JPEG, 95, thumbFileStream);
- thumbFileStream.close();
-
- SQLiteDatabase db = mDb.getWritableDatabase();
- ContentValues values = new ContentValues();
- values.put(ImageDb.COLUMN_IMAGE_THUMBNAIL_FILENAME, thumbFile.getName());
- values.put(ImageDb.COLUMN_IMAGE_FILENAME, imageFile.getName());
- db.insert(ImageDb.TABLE_NAME, null, values);
- } catch (IOException e) {
- Log.e(TAG, "Failed writing images to storage " + e);
- }
- }
-
- static class ImageDb extends SQLiteOpenHelper {
- final static int DB_VERSION = 1;
- final static String DB_NAME = "saved_wallpaper_images.db";
- final static String TABLE_NAME = "saved_wallpaper_images";
- final static String COLUMN_ID = "id";
- final static String COLUMN_IMAGE_THUMBNAIL_FILENAME = "image_thumbnail";
- final static String COLUMN_IMAGE_FILENAME = "image";
-
- Context mContext;
-
- public ImageDb(Context context) {
- super(context, new File(context.getCacheDir(), DB_NAME).getPath(), null, DB_VERSION);
- // Store the context for later use
- mContext = context;
- }
-
- @Override
- public void onCreate(SQLiteDatabase database) {
- database.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" +
- COLUMN_ID + " INTEGER NOT NULL, " +
- COLUMN_IMAGE_THUMBNAIL_FILENAME + " TEXT NOT NULL, " +
- COLUMN_IMAGE_FILENAME + " TEXT NOT NULL, " +
- "PRIMARY KEY (" + COLUMN_ID + " ASC) " +
- ");");
- }
-
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- if (oldVersion != newVersion) {
- // Delete all the records; they'll be repopulated as this is a cache
- db.execSQL("DELETE FROM " + TABLE_NAME);
- }
- }
- }
-}
diff --git a/src/com/android/launcher3/ScrimView.java b/src/com/android/launcher3/ScrimView.java
index 6831fe3d4..68200fe64 100644
--- a/src/com/android/launcher3/ScrimView.java
+++ b/src/com/android/launcher3/ScrimView.java
@@ -17,11 +17,8 @@
package com.android.launcher3;
import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
import android.graphics.Rect;
import android.util.AttributeSet;
-import android.view.View;
import android.widget.FrameLayout;
public class ScrimView extends FrameLayout implements Insettable {
diff --git a/src/com/android/launcher3/SearchDropTargetBar.java b/src/com/android/launcher3/SearchDropTargetBar.java
index 15452070c..d38c61209 100644
--- a/src/com/android/launcher3/SearchDropTargetBar.java
+++ b/src/com/android/launcher3/SearchDropTargetBar.java
@@ -69,6 +69,11 @@ public class SearchDropTargetBar extends FrameLayout implements DragController.D
dragController.setFlingToDeleteDropTarget(mDeleteDropTarget);
mInfoDropTarget.setLauncher(launcher);
mDeleteDropTarget.setLauncher(launcher);
+
+ setupQSB(launcher);
+ }
+
+ public void setupQSB(Launcher launcher) {
mQSBSearchBar = launcher.getQsbBar();
if (mEnableDropDownDropTargets) {
mQSBSearchBarAnim = LauncherAnimUtils.ofFloat(mQSBSearchBar, "translationY", 0,
@@ -188,7 +193,7 @@ public class SearchDropTargetBar extends FrameLayout implements DragController.D
// Animate out the QSB search bar, and animate in the drop target bar
prepareStartAnimation(mDropTargetBar);
mDropTargetBarAnim.start();
- if (!mIsSearchBarHidden) {
+ if (!mIsSearchBarHidden || mQSBSearchBar.getAlpha() > 0f) {
prepareStartAnimation(mQSBSearchBar);
mQSBSearchBarAnim.start();
}
@@ -204,7 +209,7 @@ public class SearchDropTargetBar extends FrameLayout implements DragController.D
// Restore the QSB search bar, and animate out the drop target bar
prepareStartAnimation(mDropTargetBar);
mDropTargetBarAnim.reverse();
- if (!mIsSearchBarHidden) {
+ if (!mIsSearchBarHidden || mQSBSearchBar.getAlpha() < 1f) {
prepareStartAnimation(mQSBSearchBar);
mQSBSearchBarAnim.reverse();
}
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index 0998eec2b..a13aaa911 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -20,9 +20,9 @@ import android.content.ComponentName;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.PackageInfo;
import android.graphics.Bitmap;
import android.util.Log;
@@ -64,6 +64,12 @@ class ShortcutInfo extends ItemInfo {
long firstInstallTime;
int flags = 0;
+ /**
+ * If this shortcut is a placeholder, then intent will be a market intent for the package, and
+ * this will hold the original intent from the database. Otherwise, null.
+ */
+ Intent restoredIntent;
+
ShortcutInfo() {
itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT;
}
@@ -71,7 +77,29 @@ class ShortcutInfo extends ItemInfo {
protected Intent getIntent() {
return intent;
}
-
+
+ protected Intent getRestoredIntent() {
+ return restoredIntent;
+ }
+
+ /**
+ * Overwrite placeholder data with restored data, or do nothing if this is not a placeholder.
+ */
+ public void restore() {
+ if (restoredIntent != null) {
+ intent = restoredIntent;
+ restoredIntent = null;
+ }
+ }
+
+
+ ShortcutInfo(Intent intent, CharSequence title, Bitmap icon) {
+ this();
+ this.intent = intent;
+ this.title = title;
+ mIcon = icon;
+ }
+
public ShortcutInfo(Context context, ShortcutInfo info) {
super(info);
title = info.title.toString();
diff --git a/src/com/android/launcher3/SlidingUpPanelLayout.java b/src/com/android/launcher3/SlidingUpPanelLayout.java
new file mode 100644
index 000000000..52deeb355
--- /dev/null
+++ b/src/com/android/launcher3/SlidingUpPanelLayout.java
@@ -0,0 +1,1315 @@
+package com.android.launcher3;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.v4.view.MotionEventCompat;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.widget.ViewDragHelper;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.SoundEffectConstants;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.animation.Animation;
+import android.view.animation.TranslateAnimation;
+
+public class SlidingUpPanelLayout extends ViewGroup {
+ private static final String TAG = SlidingUpPanelLayout.class.getSimpleName();
+
+ /**
+ * Default peeking out panel height
+ */
+ private static final int DEFAULT_PANEL_HEIGHT = 68; // dp;
+
+ /**
+ * Default height of the shadow above the peeking out panel
+ */
+ private static final int DEFAULT_SHADOW_HEIGHT = 4; // dp;
+
+ /**
+ * If no fade color is given by default it will fade to 80% gray.
+ */
+ private static final int DEFAULT_FADE_COLOR = 0x99000000;
+
+ /**
+ * Default Minimum velocity that will be detected as a fling
+ */
+ private static final int DEFAULT_MIN_FLING_VELOCITY = 400; // dips per second
+ /**
+ * Default is set to false because that is how it was written
+ */
+ private static final boolean DEFAULT_OVERLAY_FLAG = false;
+ /**
+ * Default attributes for layout
+ */
+ private static final int[] DEFAULT_ATTRS = new int[] {
+ android.R.attr.gravity
+ };
+
+ /**
+ * Minimum velocity that will be detected as a fling
+ */
+ private int mMinFlingVelocity = DEFAULT_MIN_FLING_VELOCITY;
+
+ /**
+ * The fade color used for the panel covered by the slider. 0 = no fading.
+ */
+ private int mCoveredFadeColor = DEFAULT_FADE_COLOR;
+
+ /**
+ * Default paralax length of the main view
+ */
+ private static final int DEFAULT_PARALAX_OFFSET = 0;
+
+ /**
+ * The paint used to dim the main layout when sliding
+ */
+ private final Paint mCoveredFadePaint = new Paint();
+
+ /**
+ * Drawable used to draw the shadow between panes.
+ */
+ private final Drawable mShadowDrawable;
+
+ /**
+ * The size of the overhang in pixels.
+ */
+ private int mPanelHeight = -1;
+
+ /**
+ * The size of the shadow in pixels.
+ */
+ private int mShadowHeight = -1;
+
+ /**
+ * Paralax offset
+ */
+ private int mParalaxOffset = -1;
+
+ /**
+ * True if the collapsed panel should be dragged up.
+ */
+ private boolean mIsSlidingUp;
+
+ /**
+ * True if a panel can slide with the current measurements
+ */
+ private boolean mCanSlide;
+
+ /**
+ * Panel overlays the windows instead of putting it underneath it.
+ */
+ private boolean mOverlayContent = DEFAULT_OVERLAY_FLAG;
+
+ /**
+ * If provided, the panel can be dragged by only this view. Otherwise, the entire panel can be
+ * used for dragging.
+ */
+ private View mDragView;
+
+ /**
+ * If provided, the panel can be dragged by only this view. Otherwise, the entire panel can be
+ * used for dragging.
+ */
+ private int mDragViewResId = -1;
+
+ /**
+ * The child view that can slide, if any.
+ */
+ private View mSlideableView;
+
+ /**
+ * The main view
+ */
+ private View mMainView;
+
+ /**
+ * Current state of the slideable view.
+ */
+ private enum SlideState {
+ EXPANDED,
+ COLLAPSED,
+ ANCHORED
+ }
+ private SlideState mSlideState = SlideState.COLLAPSED;
+
+ /**
+ * How far the panel is offset from its expanded position.
+ * range [0, 1] where 0 = expanded, 1 = collapsed.
+ */
+ private float mSlideOffset;
+
+ /**
+ * How far in pixels the slideable panel may move.
+ */
+ private int mSlideRange;
+
+ /**
+ * A panel view is locked into internal scrolling or another condition that
+ * is preventing a drag.
+ */
+ private boolean mIsUnableToDrag;
+
+ /**
+ * Flag indicating that sliding feature is enabled\disabled
+ */
+ private boolean mIsSlidingEnabled;
+
+ /**
+ * Flag indicating if a drag view can have its own touch events. If set
+ * to true, a drag view can scroll horizontally and have its own click listener.
+ *
+ * Default is set to false.
+ */
+ private boolean mIsUsingDragViewTouchEvents;
+
+ /**
+ * Threshold to tell if there was a scroll touch event.
+ */
+ private final int mScrollTouchSlop;
+
+ private float mInitialMotionX;
+ private float mInitialMotionY;
+ private float mAnchorPoint = 0.f;
+ private TranslateAnimation mAnimation;
+
+ private PanelSlideListener mPanelSlideListener;
+
+ private final ViewDragHelper mDragHelper;
+
+ /**
+ * Stores whether or not the pane was expanded the last time it was slideable.
+ * If expand/collapse operations are invoked this state is modified. Used by
+ * instance state save/restore.
+ */
+ private boolean mFirstLayout = true;
+
+ private final Rect mTmpRect = new Rect();
+
+ /**
+ * Listener for monitoring events about sliding panes.
+ */
+ public interface PanelSlideListener {
+ /**
+ * Called when a sliding pane's position changes.
+ * @param panel The child view that was moved
+ * @param slideOffset The new offset of this sliding pane within its range, from 0-1
+ */
+ public void onPanelSlide(View panel, float slideOffset);
+ /**
+ * Called when a sliding pane becomes slid completely collapsed. The pane may or may not
+ * be interactive at this point depending on if it's shown or hidden
+ * @param panel The child view that was slid to an collapsed position, revealing other panes
+ */
+ public void onPanelCollapsed(View panel);
+
+ /**
+ * Called when a sliding pane becomes slid completely expanded. The pane is now guaranteed
+ * to be interactive. It may now obscure other views in the layout.
+ * @param panel The child view that was slid to a expanded position
+ */
+ public void onPanelExpanded(View panel);
+
+ public void onPanelAnchored(View panel);
+ }
+
+ /**
+ * No-op stubs for {@link PanelSlideListener}. If you only want to implement a subset
+ * of the listener methods you can extend this instead of implement the full interface.
+ */
+ public static class SimplePanelSlideListener implements PanelSlideListener {
+ @Override
+ public void onPanelSlide(View panel, float slideOffset) {
+ }
+ @Override
+ public void onPanelCollapsed(View panel) {
+ }
+ @Override
+ public void onPanelExpanded(View panel) {
+ }
+ @Override
+ public void onPanelAnchored(View panel) {
+ }
+ }
+
+ public SlidingUpPanelLayout(Context context) {
+ this(context, null);
+ }
+
+ public SlidingUpPanelLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public SlidingUpPanelLayout(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ if (attrs != null) {
+ TypedArray defAttrs = context.obtainStyledAttributes(attrs, DEFAULT_ATTRS);
+
+ if (defAttrs != null) {
+ int gravity = defAttrs.getInt(0, Gravity.NO_GRAVITY);
+ if (gravity != Gravity.TOP && gravity != Gravity.BOTTOM) {
+ throw new IllegalArgumentException("gravity must be set to either top or bottom");
+ }
+ mIsSlidingUp = gravity == Gravity.BOTTOM;
+ }
+
+ defAttrs.recycle();
+
+ TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SlidingUpPanelLayout);
+
+ if (ta != null) {
+ mPanelHeight = ta.getDimensionPixelSize(R.styleable.SlidingUpPanelLayout_panelHeight, -1);
+ mShadowHeight = ta.getDimensionPixelSize(R.styleable.SlidingUpPanelLayout_shadowHeight, -1);
+ mParalaxOffset = ta.getDimensionPixelSize(R.styleable.SlidingUpPanelLayout_paralaxOffset, -1);
+
+ mMinFlingVelocity = ta.getInt(R.styleable.SlidingUpPanelLayout_flingVelocity, DEFAULT_MIN_FLING_VELOCITY);
+ mCoveredFadeColor = ta.getColor(R.styleable.SlidingUpPanelLayout_fadeColor, DEFAULT_FADE_COLOR);
+
+ mDragViewResId = ta.getResourceId(R.styleable.SlidingUpPanelLayout_dragView, -1);
+
+ mOverlayContent = ta.getBoolean(R.styleable.SlidingUpPanelLayout_overlay,DEFAULT_OVERLAY_FLAG);
+ }
+
+ ta.recycle();
+ }
+
+ final float density = context.getResources().getDisplayMetrics().density;
+ if (mPanelHeight == -1) {
+ mPanelHeight = (int) (DEFAULT_PANEL_HEIGHT * density + 0.5f);
+ }
+ if (mShadowHeight == -1) {
+ mShadowHeight = (int) (DEFAULT_SHADOW_HEIGHT * density + 0.5f);
+ }
+ if (mParalaxOffset == -1) {
+ mParalaxOffset = (int) (DEFAULT_PARALAX_OFFSET * density);
+ }
+ // If the shadow height is zero, don't show the shadow
+ if (mShadowHeight > 0) {
+ if (mIsSlidingUp) {
+ mShadowDrawable = getResources().getDrawable(R.drawable.above_shadow);
+ } else {
+ mShadowDrawable = getResources().getDrawable(R.drawable.below_shadow);
+ }
+
+ } else {
+ mShadowDrawable = null;
+ }
+
+ setWillNotDraw(false);
+
+ mDragHelper = ViewDragHelper.create(this, 0.5f, new DragHelperCallback());
+ mDragHelper.setMinVelocity(mMinFlingVelocity * density);
+
+ mCanSlide = true;
+ mIsSlidingEnabled = true;
+
+ ViewConfiguration vc = ViewConfiguration.get(context);
+ mScrollTouchSlop = vc.getScaledTouchSlop();
+ }
+
+ /**
+ * Set the Drag View after the view is inflated
+ */
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ if (mDragViewResId != -1) {
+ mDragView = findViewById(mDragViewResId);
+ }
+ }
+
+ /**
+ * Set the color used to fade the pane covered by the sliding pane out when the pane
+ * will become fully covered in the expanded state.
+ *
+ * @param color An ARGB-packed color value
+ */
+ public void setCoveredFadeColor(int color) {
+ mCoveredFadeColor = color;
+ invalidate();
+ }
+
+ /**
+ * @return The ARGB-packed color value used to fade the fixed pane
+ */
+ public int getCoveredFadeColor() {
+ return mCoveredFadeColor;
+ }
+
+ /**
+ * Set sliding enabled flag
+ * @param enabled flag value
+ */
+ public void setSlidingEnabled(boolean enabled) {
+ mIsSlidingEnabled = enabled;
+ }
+
+ /**
+ * Set the collapsed panel height in pixels
+ *
+ * @param val A height in pixels
+ */
+ public void setPanelHeight(int val) {
+ mPanelHeight = val;
+ requestLayout();
+ }
+
+ /**
+ * @return The current collapsed panel height
+ */
+ public int getPanelHeight() {
+ return mPanelHeight;
+ }
+
+ /**
+ * @return The current paralax offset
+ */
+ public int getCurrentParalaxOffset() {
+ int offset = (int)(mParalaxOffset * (1 - mSlideOffset));
+ return mIsSlidingUp ? -offset : offset;
+ }
+
+ /**
+ * Sets the panel slide listener
+ * @param listener
+ */
+ public void setPanelSlideListener(PanelSlideListener listener) {
+ mPanelSlideListener = listener;
+ }
+
+ /**
+ * Set the draggable view portion. Use to null, to allow the whole panel to be draggable
+ *
+ * @param dragView A view that will be used to drag the panel.
+ */
+ public void setDragView(View dragView) {
+ mDragView = dragView;
+ }
+
+ /**
+ * Set an anchor point where the panel can stop during sliding
+ *
+ * @param anchorPoint A value between 0 and 1, determining the position of the anchor point
+ * starting from the top of the layout.
+ */
+ public void setAnchorPoint(float anchorPoint) {
+ if (anchorPoint > 0 && anchorPoint < 1)
+ mAnchorPoint = anchorPoint;
+ }
+
+ /**
+ * Sets whether or not the panel overlays the content
+ * @param overlayed
+ */
+ public void setOverlayed(boolean overlayed) {
+ mOverlayContent = overlayed;
+ }
+
+ /**
+ * Check if the panel is set as an overlay.
+ */
+ public boolean isOverlayed() {
+ return mOverlayContent;
+ }
+
+ void dispatchOnPanelSlide(View panel) {
+ if (mPanelSlideListener != null) {
+ mPanelSlideListener.onPanelSlide(panel, mSlideOffset);
+ }
+ }
+
+ void dispatchOnPanelExpanded(View panel) {
+ if (mPanelSlideListener != null) {
+ mPanelSlideListener.onPanelExpanded(panel);
+ }
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ }
+
+ void dispatchOnPanelCollapsed(View panel) {
+ if (mPanelSlideListener != null) {
+ mPanelSlideListener.onPanelCollapsed(panel);
+ }
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ }
+
+ void dispatchOnPanelAnchored(View panel) {
+ if (mPanelSlideListener != null) {
+ mPanelSlideListener.onPanelAnchored(panel);
+ }
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ }
+
+ void updateObscuredViewVisibility() {
+ if (getChildCount() == 0) {
+ return;
+ }
+ final int leftBound = getPaddingLeft();
+ final int rightBound = getWidth() - getPaddingRight();
+ final int topBound = getPaddingTop();
+ final int bottomBound = getHeight() - getPaddingBottom();
+ final int left;
+ final int right;
+ final int top;
+ final int bottom;
+ if (mSlideableView != null && hasOpaqueBackground(mSlideableView)) {
+ left = mSlideableView.getLeft();
+ right = mSlideableView.getRight();
+ top = mSlideableView.getTop();
+ bottom = mSlideableView.getBottom();
+ } else {
+ left = right = top = bottom = 0;
+ }
+ View child = getChildAt(0);
+ final int clampedChildLeft = Math.max(leftBound, child.getLeft());
+ final int clampedChildTop = Math.max(topBound, child.getTop());
+ final int clampedChildRight = Math.min(rightBound, child.getRight());
+ final int clampedChildBottom = Math.min(bottomBound, child.getBottom());
+ final int vis;
+ if (clampedChildLeft >= left && clampedChildTop >= top &&
+ clampedChildRight <= right && clampedChildBottom <= bottom) {
+ vis = INVISIBLE;
+ } else {
+ vis = VISIBLE;
+ }
+ child.setVisibility(vis);
+ }
+
+ void setAllChildrenVisible() {
+ for (int i = 0, childCount = getChildCount(); i < childCount; i++) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() == INVISIBLE) {
+ child.setVisibility(VISIBLE);
+ }
+ }
+ }
+
+ private static boolean hasOpaqueBackground(View v) {
+ final Drawable bg = v.getBackground();
+ if (bg != null) {
+ return bg.getOpacity() == PixelFormat.OPAQUE;
+ }
+ return false;
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mFirstLayout = true;
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mFirstLayout = true;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+ final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+ final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+
+ if (widthMode != MeasureSpec.EXACTLY) {
+ throw new IllegalStateException("Width must have an exact value or MATCH_PARENT");
+ } else if (heightMode != MeasureSpec.EXACTLY) {
+ throw new IllegalStateException("Height must have an exact value or MATCH_PARENT");
+ }
+
+ int layoutHeight = heightSize - getPaddingTop() - getPaddingBottom();
+ int panelHeight = mPanelHeight;
+
+ final int childCount = getChildCount();
+
+ if (childCount > 2) {
+ Log.e(TAG, "onMeasure: More than two child views are not supported.");
+ } else if (getChildAt(1).getVisibility() == GONE) {
+ panelHeight = 0;
+ }
+
+ // We'll find the current one below.
+ mSlideableView = null;
+ mCanSlide = false;
+
+ // First pass. Measure based on child LayoutParams width/height.
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+ int height = layoutHeight;
+ if (child.getVisibility() == GONE) {
+ lp.dimWhenOffset = false;
+ continue;
+ }
+
+ if (i == 1) {
+ lp.slideable = true;
+ lp.dimWhenOffset = true;
+ mSlideableView = child;
+ mCanSlide = true;
+ } else {
+ if (!mOverlayContent) {
+ height -= panelHeight;
+ }
+ mMainView = child;
+ }
+
+ int childWidthSpec;
+ if (lp.width == LayoutParams.WRAP_CONTENT) {
+ childWidthSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.AT_MOST);
+ } else if (lp.width == LayoutParams.MATCH_PARENT) {
+ childWidthSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);
+ } else {
+ childWidthSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
+ }
+
+ int childHeightSpec;
+ if (lp.height == LayoutParams.WRAP_CONTENT) {
+ childHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST);
+ } else if (lp.height == LayoutParams.MATCH_PARENT) {
+ childHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
+ } else {
+ childHeightSpec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
+ }
+
+ child.measure(childWidthSpec, childHeightSpec);
+ }
+
+ setMeasuredDimension(widthSize, heightSize);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ final int paddingLeft = getPaddingLeft();
+ final int paddingTop = getPaddingTop();
+ final int slidingTop = getSlidingTop();
+
+ final int childCount = getChildCount();
+
+ if (mFirstLayout) {
+ switch (mSlideState) {
+ case EXPANDED:
+ mSlideOffset = mCanSlide ? 0.f : 1.f;
+ break;
+ case ANCHORED:
+ mSlideOffset = mCanSlide ? mAnchorPoint : 1.f;
+ break;
+ default:
+ mSlideOffset = 1.f;
+ break;
+ }
+ }
+
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+
+ if (child.getVisibility() == GONE) {
+ continue;
+ }
+
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ final int childHeight = child.getMeasuredHeight();
+
+ if (lp.slideable) {
+ mSlideRange = childHeight - mPanelHeight;
+ }
+
+ int childTop;
+ if (mIsSlidingUp) {
+ childTop = lp.slideable ? slidingTop + (int) (mSlideRange * mSlideOffset) : paddingTop;
+ } else {
+ childTop = lp.slideable ? slidingTop - (int) (mSlideRange * mSlideOffset) : paddingTop;
+ if (!lp.slideable && !mOverlayContent) {
+ childTop += mPanelHeight;
+ }
+ }
+ final int childBottom = childTop + childHeight;
+ final int childLeft = paddingLeft;
+ final int childRight = childLeft + child.getMeasuredWidth();
+
+ child.layout(childLeft, childTop, childRight, childBottom);
+ }
+
+ if (mFirstLayout) {
+ updateObscuredViewVisibility();
+ }
+
+ mFirstLayout = false;
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ // Recalculate sliding panes and their details
+ if (h != oldh) {
+ mFirstLayout = true;
+ }
+ }
+
+ /**
+ * Set if the drag view can have its own touch events. If set
+ * to true, a drag view can scroll horizontally and have its own click listener.
+ *
+ * Default is set to false.
+ */
+ public void setEnableDragViewTouchEvents(boolean enabled) {
+ mIsUsingDragViewTouchEvents = enabled;
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ final int action = MotionEventCompat.getActionMasked(ev);
+
+ if (mAnimation != null || !mCanSlide || !mIsSlidingEnabled || (mIsUnableToDrag && action != MotionEvent.ACTION_DOWN)) {
+ mDragHelper.cancel();
+ return super.onInterceptTouchEvent(ev);
+ }
+
+ if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
+ mDragHelper.cancel();
+ return false;
+ }
+
+ final float x = ev.getX();
+ final float y = ev.getY();
+ boolean interceptTap = false;
+
+ switch (action) {
+ case MotionEvent.ACTION_DOWN: {
+ mIsUnableToDrag = false;
+ mInitialMotionX = x;
+ mInitialMotionY = y;
+ if (isDragViewUnder((int) x, (int) y) && !mIsUsingDragViewTouchEvents) {
+ interceptTap = true;
+ }
+ break;
+ }
+
+ case MotionEvent.ACTION_MOVE: {
+ final float adx = Math.abs(x - mInitialMotionX);
+ final float ady = Math.abs(y - mInitialMotionY);
+ final int dragSlop = mDragHelper.getTouchSlop();
+
+ // Handle any horizontal scrolling on the drag view.
+ if (mIsUsingDragViewTouchEvents) {
+ if (adx > mScrollTouchSlop && ady < mScrollTouchSlop) {
+ return super.onInterceptTouchEvent(ev);
+ }
+ // Intercept the touch if the drag view has any vertical scroll.
+ // onTouchEvent will determine if the view should drag vertically.
+ else if (ady > mScrollTouchSlop) {
+ interceptTap = isDragViewUnder((int) x, (int) y);
+ }
+ }
+
+ if ((ady > dragSlop && adx > ady) || !isDragViewUnder((int) x, (int) y)) {
+ mDragHelper.cancel();
+ mIsUnableToDrag = true;
+ return false;
+ }
+ break;
+ }
+ }
+
+ final boolean interceptForDrag = mDragHelper.shouldInterceptTouchEvent(ev);
+
+ return interceptForDrag;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ if (!mCanSlide || !mIsSlidingEnabled || mAnimation != null) {
+ return super.onTouchEvent(ev);
+ }
+
+ mDragHelper.processTouchEvent(ev);
+
+ final int action = ev.getAction();
+ boolean wantTouchEvents = true;
+
+ switch (action & MotionEventCompat.ACTION_MASK) {
+ case MotionEvent.ACTION_DOWN: {
+ final float x = ev.getX();
+ final float y = ev.getY();
+
+ //Fix to allow both SettingPanel Drag and Workspace Drag
+ if (mSlideState == SlideState.COLLAPSED) {
+ if (y < mSlideableView.getTop()) {
+ return false;
+ }
+ }
+
+ mInitialMotionX = x;
+ mInitialMotionY = y;
+ break;
+ }
+
+ case MotionEvent.ACTION_UP: {
+ final float x = ev.getX();
+ final float y = ev.getY();
+ final float dx = x - mInitialMotionX;
+ final float dy = y - mInitialMotionY;
+ final int slop = mDragHelper.getTouchSlop();
+ View dragView = mDragView != null ? mDragView : mSlideableView;
+ if (dx * dx + dy * dy < slop * slop &&
+ isDragViewUnder((int) x, (int) y)) {
+ dragView.playSoundEffect(SoundEffectConstants.CLICK);
+ if (!isExpanded() && !isAnchored()) {
+ expandPane(mAnchorPoint);
+ } else {
+ collapsePane();
+ }
+ break;
+ }
+ break;
+ }
+ }
+
+ return wantTouchEvents;
+ }
+
+ private boolean isDragViewUnder(int x, int y) {
+ View dragView = mDragView != null ? mDragView : mSlideableView;
+ if (dragView == null) return false;
+ int[] viewLocation = new int[2];
+ dragView.getLocationOnScreen(viewLocation);
+ int[] parentLocation = new int[2];
+ this.getLocationOnScreen(parentLocation);
+ int screenX = parentLocation[0] + x;
+ int screenY = parentLocation[1] + y;
+ return screenX >= viewLocation[0] && screenX < viewLocation[0] + dragView.getWidth() &&
+ screenY >= viewLocation[1] && screenY < viewLocation[1] + dragView.getHeight();
+ }
+
+ private boolean expandPane(View pane, int initialVelocity, float mSlideOffset) {
+ if (mFirstLayout || smoothSlideTo(mSlideOffset, initialVelocity)) {
+ return true;
+ }
+ return false;
+ }
+
+ private boolean collapsePane(View pane, int initialVelocity) {
+ if (mFirstLayout || smoothSlideTo(1.f, initialVelocity)) {
+ return true;
+ }
+ return false;
+ }
+
+ private int getSlidingTop() {
+ if (mSlideableView != null) {
+ return mIsSlidingUp
+ ? getMeasuredHeight() - getPaddingBottom() - mSlideableView.getMeasuredHeight()
+ : getPaddingTop();
+ }
+
+ return getMeasuredHeight() - getPaddingBottom();
+ }
+
+ /**
+ * Collapse the sliding pane if it is currently slideable. If first layout
+ * has already completed this will animate.
+ *
+ * @return true if the pane was slideable and is now collapsed/in the process of collapsing
+ */
+ public boolean collapsePane() {
+ return collapsePane(mSlideableView, 0);
+ }
+
+ /**
+ * Expand the sliding pane if it is currently slideable. If first layout
+ * has already completed this will animate.
+ *
+ * @return true if the pane was slideable and is now expanded/in the process of expading
+ */
+ public boolean expandPane() {
+ return expandPane(0);
+ }
+
+ /**
+ * Partially expand the sliding pane up to a specific offset
+ *
+ * @param mSlideOffset Value between 0 and 1, where 0 is completely expanded.
+ * @return true if the pane was slideable and is now expanded/in the process of expading
+ */
+ public boolean expandPane(float mSlideOffset) {
+ if (!isPaneVisible()) {
+ showPane();
+ }
+ return expandPane(mSlideableView, 0, mSlideOffset);
+ }
+
+ /**
+ * Check if the layout is completely expanded.
+ *
+ * @return true if sliding panels are completely expanded
+ */
+ public boolean isExpanded() {
+ return mSlideState == SlideState.EXPANDED;
+ }
+
+ /**
+ * Check if the layout is anchored in an intermediate point.
+ *
+ * @return true if sliding panels are anchored
+ */
+ public boolean isAnchored() {
+ return mSlideState == SlideState.ANCHORED;
+ }
+
+ /**
+ * Check if the content in this layout cannot fully fit side by side and therefore
+ * the content pane can be slid back and forth.
+ *
+ * @return true if content in this layout can be expanded
+ */
+ public boolean isSlideable() {
+ return mCanSlide;
+ }
+
+ public boolean isPaneVisible() {
+ if (getChildCount() < 2) {
+ return false;
+ }
+ View slidingPane = getChildAt(1);
+ return slidingPane.getVisibility() == View.VISIBLE;
+ }
+
+ public void showPane() {
+ if (getChildCount() < 2) {
+ return;
+ }
+ final View slidingPane = getChildAt(1);
+ mAnimation = new TranslateAnimation(0, 0, (mIsSlidingUp ? 1 : -1) * getPanelHeight(), 0);
+ mAnimation.setDuration(400);
+ mAnimation.setAnimationListener(new Animation.AnimationListener() {
+ @Override
+ public void onAnimationStart(Animation animation) {
+ slidingPane.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ requestLayout();
+ mAnimation = null;
+ }
+
+ @Override
+ public void onAnimationRepeat(Animation animation) {
+ }
+ });
+ slidingPane.startAnimation(mAnimation);
+ }
+
+ public void hidePane() {
+ if (mSlideableView == null) {
+ return;
+ }
+ mAnimation = new TranslateAnimation(0, 0, 0, (mIsSlidingUp ? 1 : -1) * getPanelHeight());
+ mAnimation.setDuration(500);
+ mAnimation.setAnimationListener(new Animation.AnimationListener() {
+ @Override
+ public void onAnimationStart(Animation animation) {
+
+ }
+
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ mSlideableView.setVisibility(View.GONE);
+ requestLayout();
+ mAnimation = null;
+ }
+
+ @Override
+ public void onAnimationRepeat(Animation animation) {
+
+ }
+ });
+ mSlideableView.startAnimation(mAnimation);
+ }
+
+ private void onPanelDragged(int newTop) {
+ final int topBound = getSlidingTop();
+ mSlideOffset = mIsSlidingUp
+ ? (float) (newTop - topBound) / mSlideRange
+ : (float) (topBound - newTop) / mSlideRange;
+ dispatchOnPanelSlide(mSlideableView);
+
+ if (mParalaxOffset > 0) {
+ int mainViewOffset = getCurrentParalaxOffset();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ mMainView.setTranslationY(mainViewOffset);
+ } else {
+ mMainView.animate().translationY(mainViewOffset);
+ }
+ }
+ }
+
+ @Override
+ protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ boolean result;
+ final int save = canvas.save(Canvas.CLIP_SAVE_FLAG);
+
+ boolean drawScrim = false;
+
+ if (mCanSlide && !lp.slideable && mSlideableView != null) {
+ // Clip against the slider; no sense drawing what will immediately be covered,
+ // Unless the panel is set to overlay content
+ if (!mOverlayContent) {
+ canvas.getClipBounds(mTmpRect);
+ if (mIsSlidingUp) {
+ mTmpRect.bottom = Math.min(mTmpRect.bottom, mSlideableView.getTop());
+ } else {
+ mTmpRect.top = Math.max(mTmpRect.top, mSlideableView.getBottom());
+ }
+ canvas.clipRect(mTmpRect);
+ }
+ if (mSlideOffset < 1) {
+ drawScrim = true;
+ }
+ }
+
+ result = super.drawChild(canvas, child, drawingTime);
+ canvas.restoreToCount(save);
+
+ if (drawScrim) {
+ final int baseAlpha = (mCoveredFadeColor & 0xff000000) >>> 24;
+ final int imag = (int) (baseAlpha * (1 - mSlideOffset));
+ final int color = imag << 24 | (mCoveredFadeColor & 0xffffff);
+ mCoveredFadePaint.setColor(color);
+ canvas.drawRect(mTmpRect, mCoveredFadePaint);
+ }
+
+ return result;
+ }
+
+ /**
+ * Smoothly animate mDraggingPane to the target X position within its range.
+ *
+ * @param slideOffset position to animate to
+ * @param velocity initial velocity in case of fling, or 0.
+ */
+ boolean smoothSlideTo(float slideOffset, int velocity) {
+ if (!mCanSlide) {
+ // Nothing to do.
+ return false;
+ }
+
+ final int topBound = getSlidingTop();
+ int y = mIsSlidingUp
+ ? (int) (topBound + slideOffset * mSlideRange)
+ : (int) (topBound - slideOffset * mSlideRange);
+
+ if (mDragHelper.smoothSlideViewTo(mSlideableView, mSlideableView.getLeft(), y)) {
+ setAllChildrenVisible();
+ ViewCompat.postInvalidateOnAnimation(this);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void computeScroll() {
+ if (mDragHelper.continueSettling(true)) {
+ if (!mCanSlide) {
+ mDragHelper.abort();
+ return;
+ }
+
+ ViewCompat.postInvalidateOnAnimation(this);
+ }
+ }
+
+ @Override
+ public void draw(Canvas c) {
+ super.draw(c);
+
+ if (mSlideableView == null) {
+ // No need to draw a shadow if we don't have one.
+ return;
+ }
+
+ final int right = mSlideableView.getRight();
+ final int top;
+ final int bottom;
+ if (mIsSlidingUp) {
+ top = mSlideableView.getTop() - mShadowHeight;
+ bottom = mSlideableView.getTop();
+ } else {
+ top = mSlideableView.getBottom();
+ bottom = mSlideableView.getBottom() + mShadowHeight;
+ }
+ final int left = mSlideableView.getLeft();
+
+ if (mShadowDrawable != null) {
+ mShadowDrawable.setBounds(left, top, right, bottom);
+ mShadowDrawable.draw(c);
+ }
+ }
+
+ /**
+ * Tests scrollability within child views of v given a delta of dx.
+ *
+ * @param v View to test for horizontal scrollability
+ * @param checkV Whether the view v passed should itself be checked for scrollability (true),
+ * or just its children (false).
+ * @param dx Delta scrolled in pixels
+ * @param x X coordinate of the active touch point
+ * @param y Y coordinate of the active touch point
+ * @return true if child views of v can be scrolled by delta of dx.
+ */
+ protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
+ if (v instanceof ViewGroup) {
+ final ViewGroup group = (ViewGroup) v;
+ final int scrollX = v.getScrollX();
+ final int scrollY = v.getScrollY();
+ final int count = group.getChildCount();
+ // Count backwards - let topmost views consume scroll distance first.
+ for (int i = count - 1; i >= 0; i--) {
+ final View child = group.getChildAt(i);
+ if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&
+ y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&
+ canScroll(child, true, dx, x + scrollX - child.getLeft(),
+ y + scrollY - child.getTop())) {
+ return true;
+ }
+ }
+ }
+ return checkV && ViewCompat.canScrollHorizontally(v, -dx);
+ }
+
+
+ @Override
+ protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
+ return new LayoutParams();
+ }
+
+ @Override
+ protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+ return p instanceof MarginLayoutParams
+ ? new LayoutParams((MarginLayoutParams) p)
+ : new LayoutParams(p);
+ }
+
+ @Override
+ protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+ return p instanceof LayoutParams && super.checkLayoutParams(p);
+ }
+
+ @Override
+ public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
+ return new LayoutParams(getContext(), attrs);
+ }
+
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ Parcelable superState = super.onSaveInstanceState();
+
+ SavedState ss = new SavedState(superState);
+ ss.mSlideState = mSlideState;
+
+ return ss;
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ SavedState ss = (SavedState) state;
+ super.onRestoreInstanceState(ss.getSuperState());
+ mSlideState = ss.mSlideState;
+ }
+
+ private class DragHelperCallback extends ViewDragHelper.Callback {
+
+ @Override
+ public boolean tryCaptureView(View child, int pointerId) {
+ if (mIsUnableToDrag) {
+ return false;
+ }
+
+ return ((LayoutParams) child.getLayoutParams()).slideable;
+ }
+
+ @Override
+ public void onViewDragStateChanged(int state) {
+ int anchoredTop = (int)(mAnchorPoint*mSlideRange);
+
+ if (mDragHelper.getViewDragState() == ViewDragHelper.STATE_IDLE) {
+ if (mSlideOffset == 0) {
+ if (mSlideState != SlideState.EXPANDED) {
+ updateObscuredViewVisibility();
+ dispatchOnPanelExpanded(mSlideableView);
+ mSlideState = SlideState.EXPANDED;
+ }
+ } else if (mSlideOffset == (float)anchoredTop/(float)mSlideRange) {
+ if (mSlideState != SlideState.ANCHORED) {
+ updateObscuredViewVisibility();
+ dispatchOnPanelAnchored(mSlideableView);
+ mSlideState = SlideState.ANCHORED;
+ }
+ } else if (mSlideState != SlideState.COLLAPSED) {
+ dispatchOnPanelCollapsed(mSlideableView);
+ mSlideState = SlideState.COLLAPSED;
+ }
+ }
+ }
+
+ @Override
+ public void onViewCaptured(View capturedChild, int activePointerId) {
+ // Make all child views visible in preparation for sliding things around
+ setAllChildrenVisible();
+ }
+
+ @Override
+ public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
+ onPanelDragged(top);
+ invalidate();
+ }
+
+ @Override
+ public void onViewReleased(View releasedChild, float xvel, float yvel) {
+ int top = mIsSlidingUp
+ ? getSlidingTop()
+ : getSlidingTop() - mSlideRange;
+
+ if (mAnchorPoint != 0) {
+ int anchoredTop;
+ float anchorOffset;
+ if (mIsSlidingUp) {
+ anchoredTop = (int)(mAnchorPoint*mSlideRange);
+ anchorOffset = (float)anchoredTop/(float)mSlideRange;
+ } else {
+ anchoredTop = mPanelHeight - (int)(mAnchorPoint*mSlideRange);
+ anchorOffset = (float)(mPanelHeight - anchoredTop)/(float)mSlideRange;
+ }
+
+ if (yvel > 0 || (yvel == 0 && mSlideOffset >= (1f+anchorOffset)/2)) {
+ top += mSlideRange;
+ } else if (yvel == 0 && mSlideOffset < (1f+anchorOffset)/2
+ && mSlideOffset >= anchorOffset/2) {
+ top += mSlideRange * mAnchorPoint;
+ }
+
+ } else if (yvel > 0 || (yvel == 0 && mSlideOffset > 0.5f)) {
+ top += mSlideRange;
+ }
+
+ mDragHelper.settleCapturedViewAt(releasedChild.getLeft(), top);
+ invalidate();
+ }
+
+ @Override
+ public int getViewVerticalDragRange(View child) {
+ return mSlideRange;
+ }
+
+ @Override
+ public int clampViewPositionVertical(View child, int top, int dy) {
+ final int topBound;
+ final int bottomBound;
+ if (mIsSlidingUp) {
+ topBound = getSlidingTop();
+ bottomBound = topBound + mSlideRange;
+ } else {
+ bottomBound = getPaddingTop();
+ topBound = bottomBound - mSlideRange;
+ }
+
+ return Math.min(Math.max(top, topBound), bottomBound);
+ }
+ }
+
+ public static class LayoutParams extends ViewGroup.MarginLayoutParams {
+ private static final int[] ATTRS = new int[] {
+ android.R.attr.layout_weight
+ };
+
+ /**
+ * True if this pane is the slideable pane in the layout.
+ */
+ boolean slideable;
+
+ /**
+ * True if this view should be drawn dimmed
+ * when it's been offset from its default position.
+ */
+ boolean dimWhenOffset;
+
+ Paint dimPaint;
+
+ public LayoutParams() {
+ super(MATCH_PARENT, MATCH_PARENT);
+ }
+
+ public LayoutParams(int width, int height) {
+ super(width, height);
+ }
+
+ public LayoutParams(android.view.ViewGroup.LayoutParams source) {
+ super(source);
+ }
+
+ public LayoutParams(MarginLayoutParams source) {
+ super(source);
+ }
+
+ public LayoutParams(LayoutParams source) {
+ super(source);
+ }
+
+ public LayoutParams(Context c, AttributeSet attrs) {
+ super(c, attrs);
+
+ final TypedArray a = c.obtainStyledAttributes(attrs, ATTRS);
+ a.recycle();
+ }
+
+ }
+
+ static class SavedState extends BaseSavedState {
+ SlideState mSlideState;
+
+ SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ private SavedState(Parcel in) {
+ super(in);
+ try {
+ mSlideState = Enum.valueOf(SlideState.class, in.readString());
+ } catch (IllegalArgumentException e) {
+ mSlideState = SlideState.COLLAPSED;
+ }
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ out.writeString(mSlideState.toString());
+ }
+
+ public static final Parcelable.Creator<SavedState> CREATOR =
+ new Parcelable.Creator<SavedState>() {
+ @Override
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ @Override
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+}
diff --git a/src/com/android/launcher3/SmoothPagedView.java b/src/com/android/launcher3/SmoothPagedView.java
index a45dbbf6e..4e331aa2c 100644
--- a/src/com/android/launcher3/SmoothPagedView.java
+++ b/src/com/android/launcher3/SmoothPagedView.java
@@ -19,7 +19,6 @@ package com.android.launcher3;
import android.content.Context;
import android.util.AttributeSet;
import android.view.animation.Interpolator;
-import android.widget.Scroller;
public abstract class SmoothPagedView extends PagedView {
private static final float SMOOTHING_SPEED = 0.75f;
@@ -52,8 +51,6 @@ public abstract class SmoothPagedView extends PagedView {
}
public float getInterpolation(float t) {
- // _o(t) = t * t * ((tension + 1) * t + tension)
- // o(t) = _o(t - 1) + 1
t -= 1.0f;
return t * t * ((mTension + 1) * t + mTension) + 1.0f;
}
@@ -102,7 +99,7 @@ public abstract class SmoothPagedView extends PagedView {
mBaseLineFlingVelocity = 2500.0f;
mFlingVelocityInfluence = 0.4f;
mScrollInterpolator = new OvershootInterpolator();
- mScroller = new Scroller(getContext(), mScrollInterpolator);
+ setDefaultInterpolator(mScrollInterpolator);
}
}
diff --git a/src/com/android/launcher3/ThemeChangedReceiver.java b/src/com/android/launcher3/ThemeChangedReceiver.java
new file mode 100644
index 000000000..c7a98c5ab
--- /dev/null
+++ b/src/com/android/launcher3/ThemeChangedReceiver.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2014 The CyanogenMod 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;
+
+import static com.android.launcher3.WidgetPreviewLoader.CacheDb.DB_NAME;
+
+import java.io.File;
+import java.util.ArrayList;
+
+public class ThemeChangedReceiver extends BroadcastReceiver {
+ private static final String EXTRA_COMPONENTS = "components";
+
+ public static final String MODIFIES_ICONS = "mods_icons";
+ public static final String MODIFIES_FONTS = "mods_fonts";
+ public static final String MODIFIES_OVERLAYS = "mods_overlays";
+
+ public void onReceive(Context context, Intent intent) {
+ // components is a string array of the components that changed
+ ArrayList<String> components = intent.getStringArrayListExtra(EXTRA_COMPONENTS);
+ if (isInterestingThemeChange(components)) {
+ LauncherAppState app = LauncherAppState.getInstance();
+ clearWidgetPreviewCache(context);
+ app.recreateWidgetPreviewDb();
+ app.getIconCache().flush();
+ app.getModel().forceReload();
+ }
+ }
+
+ /**
+ * We consider this an "interesting" theme change if it modifies icons, overlays, or fonts.
+ * @param components
+ * @return
+ */
+ private boolean isInterestingThemeChange(ArrayList<String> components) {
+ if (components != null) {
+ for (String component : components) {
+ if (component.equals(MODIFIES_ICONS) ||
+ component.equals(MODIFIES_FONTS) ||
+ component.equals(MODIFIES_OVERLAYS)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+
+ /**
+ * Normally we could use context.deleteDatabase() but this db is in cache/ so we'll
+ * manually delete it and the journal ourselves.
+ *
+ * @param context
+ */
+ private void clearWidgetPreviewCache(Context context) {
+ File[] files = context.getCacheDir().listFiles();
+ if (files != null) {
+ for (File f : files) {
+ if (!f.isDirectory() && f.getName().startsWith(DB_NAME)) f.delete();
+ }
+ }
+ }
+}
diff --git a/src/com/android/launcher3/ThirdPartyWallpaperPickerListAdapter.java b/src/com/android/launcher3/ThirdPartyWallpaperPickerListAdapter.java
deleted file mode 100644
index 81cd5a0f0..000000000
--- a/src/com/android/launcher3/ThirdPartyWallpaperPickerListAdapter.java
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-import android.widget.FrameLayout;
-import android.widget.ListAdapter;
-import android.widget.TextView;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class ThirdPartyWallpaperPickerListAdapter extends BaseAdapter implements ListAdapter {
- private static final String LOG_TAG = "LiveWallpaperListAdapter";
-
- private final LayoutInflater mInflater;
- private final PackageManager mPackageManager;
- private final int mIconSize;
-
- private List<ThirdPartyWallpaperTile> mThirdPartyWallpaperPickers =
- new ArrayList<ThirdPartyWallpaperTile>();
-
- public static class ThirdPartyWallpaperTile extends WallpaperPickerActivity.WallpaperTileInfo {
- private ResolveInfo mResolveInfo;
- public ThirdPartyWallpaperTile(ResolveInfo resolveInfo) {
- mResolveInfo = resolveInfo;
- }
- @Override
- public void onClick(WallpaperCropActivity a) {
- final ComponentName itemComponentName = new ComponentName(
- mResolveInfo.activityInfo.packageName, mResolveInfo.activityInfo.name);
- Intent launchIntent = new Intent(Intent.ACTION_SET_WALLPAPER);
- launchIntent.setComponent(itemComponentName);
- Utilities.startActivityForResultSafely(
- a, launchIntent, WallpaperPickerActivity.PICK_WALLPAPER_THIRD_PARTY_ACTIVITY);
- }
- }
-
- public ThirdPartyWallpaperPickerListAdapter(Context context) {
- mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- mPackageManager = context.getPackageManager();
- mIconSize = context.getResources().getDimensionPixelSize(R.dimen.wallpaperItemIconSize);
- final PackageManager pm = mPackageManager;
-
- final Intent pickWallpaperIntent = new Intent(Intent.ACTION_SET_WALLPAPER);
- final List<ResolveInfo> apps =
- pm.queryIntentActivities(pickWallpaperIntent, 0);
-
- // Get list of image picker intents
- Intent pickImageIntent = new Intent(Intent.ACTION_GET_CONTENT);
- pickImageIntent.setType("image/*");
- final List<ResolveInfo> imagePickerActivities =
- pm.queryIntentActivities(pickImageIntent, 0);
- final ComponentName[] imageActivities = new ComponentName[imagePickerActivities.size()];
- for (int i = 0; i < imagePickerActivities.size(); i++) {
- ActivityInfo activityInfo = imagePickerActivities.get(i).activityInfo;
- imageActivities[i] = new ComponentName(activityInfo.packageName, activityInfo.name);
- }
-
- outerLoop:
- for (ResolveInfo info : apps) {
- final ComponentName itemComponentName =
- new ComponentName(info.activityInfo.packageName, info.activityInfo.name);
- final String itemPackageName = itemComponentName.getPackageName();
- // Exclude anything from our own package, and the old Launcher,
- // and live wallpaper picker
- if (itemPackageName.equals(context.getPackageName()) ||
- itemPackageName.equals("com.android.launcher") ||
- itemPackageName.equals("com.android.wallpaper.livepicker")) {
- continue;
- }
- // Exclude any package that already responds to the image picker intent
- for (ResolveInfo imagePickerActivityInfo : imagePickerActivities) {
- if (itemPackageName.equals(
- imagePickerActivityInfo.activityInfo.packageName)) {
- continue outerLoop;
- }
- }
- mThirdPartyWallpaperPickers.add(new ThirdPartyWallpaperTile(info));
- }
- }
-
- public int getCount() {
- return mThirdPartyWallpaperPickers.size();
- }
-
- public ThirdPartyWallpaperTile getItem(int position) {
- return mThirdPartyWallpaperPickers.get(position);
- }
-
- public long getItemId(int position) {
- return position;
- }
-
- public View getView(int position, View convertView, ViewGroup parent) {
- View view;
-
- if (convertView == null) {
- view = mInflater.inflate(R.layout.wallpaper_picker_third_party_item, parent, false);
- } else {
- view = convertView;
- }
-
- WallpaperPickerActivity.setWallpaperItemPaddingToZero((FrameLayout) view);
-
- ResolveInfo info = mThirdPartyWallpaperPickers.get(position).mResolveInfo;
- TextView label = (TextView) view.findViewById(R.id.wallpaper_item_label);
- label.setText(info.loadLabel(mPackageManager));
- Drawable icon = info.loadIcon(mPackageManager);
- icon.setBounds(new Rect(0, 0, mIconSize, mIconSize));
- label.setCompoundDrawables(null, icon, null, null);
- return view;
- }
-}
diff --git a/src/com/android/launcher3/TransitionEffectsFragment.java b/src/com/android/launcher3/TransitionEffectsFragment.java
new file mode 100644
index 000000000..88c6481d7
--- /dev/null
+++ b/src/com/android/launcher3/TransitionEffectsFragment.java
@@ -0,0 +1,220 @@
+package com.android.launcher3;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.app.Fragment;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Color;
+import android.graphics.drawable.AnimationDrawable;
+import android.os.Bundle;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.ScrollView;
+import android.widget.TextView;
+import com.android.launcher3.settings.SettingsProvider;
+
+public class TransitionEffectsFragment extends Fragment {
+ public static final String PAGE_OR_DRAWER_SCROLL_SELECT = "pageOrDrawer";
+ public static final String SELECTED_TRANSITION_EFFECT = "selectedTransitionEffect";
+ public static final String TRANSITION_EFFECTS_FRAGMENT = "transitionEffectsFragment";
+ ImageView mTransitionIcon;
+ ListView mListView;
+ View mCurrentSelection;
+ ScrollView mScrollView;
+
+ String[] mTransitionStates;
+ TypedArray mTransitionDrawables;
+ String mCurrentState;
+ int mCurrentPosition;
+ boolean mPageOrDrawer;
+ String mSettingsProviderValue;
+ int mPreferenceValue;
+
+ OnClickListener mSettingsItemListener = new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ if (mCurrentPosition == (Integer) v.getTag()) {
+ return;
+ }
+ mCurrentPosition = (Integer) v.getTag();
+ mCurrentState = mTransitionStates[mCurrentPosition];
+
+ setCleared(mCurrentSelection);
+ setSelected(v);
+ mCurrentSelection = v;
+
+ new Thread(new Runnable() {
+ public void run() {
+ mTransitionIcon.post(new Runnable() {
+ public void run() {
+ setImageViewToEffect();
+ }
+ });
+ }
+ }).start();
+
+ ((TransitionsArrayAdapter) mListView.getAdapter()).notifyDataSetChanged();
+ }
+ };
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View v = inflater.inflate(R.layout.settings_transitions_screen, container, false);
+
+ mPageOrDrawer = getArguments().getBoolean(PAGE_OR_DRAWER_SCROLL_SELECT);
+
+ mSettingsProviderValue = mPageOrDrawer ?
+ SettingsProvider.SETTINGS_UI_DRAWER_SCROLLING_TRANSITION_EFFECT
+ : SettingsProvider.SETTINGS_UI_HOMESCREEN_SCROLLING_TRANSITION_EFFECT;
+ mPreferenceValue = mPageOrDrawer ? R.string.preferences_interface_drawer_scrolling_transition_effect
+ : R.string.preferences_interface_homescreen_scrolling_transition_effect;
+
+ mTransitionIcon = (ImageView) v.findViewById(R.id.settings_transition_image);
+ mListView = (ListView) v.findViewById(R.id.settings_transitions_list);
+ mScrollView = (ScrollView) v.findViewById(R.id.scroll_view);
+ TextView title = (TextView) v.findViewById(R.id.transition_effect_title);
+ title.setText(getResources().getString(R.string.scroll_effect_text));
+ LinearLayout titleLayout = (LinearLayout) v.findViewById(R.id.transition_title);
+ titleLayout.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ setEffect();
+ }
+ });
+
+ String[] titles = getResources().getStringArray(
+ R.array.transition_effect_entries);
+ mListView.setAdapter(new TransitionsArrayAdapter(getActivity(),
+ R.layout.settings_pane_list_item, titles));
+
+ mTransitionStates = getResources().getStringArray(
+ R.array.transition_effect_values);
+ mTransitionDrawables = getResources().obtainTypedArray(
+ R.array.transition_effect_drawables);
+
+ mCurrentState = SettingsProvider.getString(getActivity(),
+ mSettingsProviderValue, mPreferenceValue);
+ mCurrentPosition = mapEffectToPosition(mCurrentState);
+
+ mListView.setSelection(mCurrentPosition);
+
+ return v;
+ }
+
+ public void setEffect() {
+ ((Launcher) getActivity()).setTransitionEffect(mPageOrDrawer, mCurrentState);
+ }
+
+ private int mapEffectToPosition(String effect) {
+ int length = mTransitionStates.length;
+ for (int i = 0; i < length; i++) {
+ if (effect.equals(mTransitionStates[i])) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ private void setImageViewToEffect() {
+ mTransitionIcon.setBackgroundResource(mTransitionDrawables
+ .getResourceId(mCurrentPosition, R.drawable.transition_none));
+
+ AnimationDrawable frameAnimation = (AnimationDrawable) mTransitionIcon.getBackground();
+ frameAnimation.start();
+ }
+
+ private void setSelected(View v) {
+ v.setBackgroundColor(Color.WHITE);
+ TextView t = (TextView) v.findViewById(R.id.item_name);
+ t.setTextColor(getResources().getColor(R.color.settings_bg_color));
+ }
+
+ private void setCleared(View v) {
+ v.setBackgroundColor(getResources().getColor(R.color.settings_bg_color));
+ TextView t = (TextView) v.findViewById(R.id.item_name);
+ t.setTextColor(Color.WHITE);
+ }
+
+ @Override
+ public Animator onCreateAnimator(int transit, boolean enter, int nextAnim) {
+ if (enter) {
+ DisplayMetrics displaymetrics = new DisplayMetrics();
+ getActivity().getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
+ int width = displaymetrics.widthPixels;
+ final ObjectAnimator anim = ObjectAnimator.ofFloat(this, "translationX", width, 0);
+
+ final View darkPanel = ((Launcher) getActivity()).getDarkPanel();
+ darkPanel.setVisibility(View.VISIBLE);
+ ObjectAnimator anim2 = ObjectAnimator.ofFloat(
+ darkPanel , "alpha", 0.0f, 0.3f);
+ anim2.start();
+
+ anim.addListener(new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator arg0) {}
+ @Override
+ public void onAnimationRepeat(Animator arg0) {}
+ @Override
+ public void onAnimationEnd(Animator arg0) {
+ darkPanel.setVisibility(View.GONE);
+ setImageViewToEffect();
+ }
+ @Override
+ public void onAnimationCancel(Animator arg0) {}
+ });
+
+ return anim;
+ } else {
+ return super.onCreateAnimator(transit, enter, nextAnim);
+ }
+ }
+
+ private class TransitionsArrayAdapter extends ArrayAdapter<String> {
+ Context mContext;
+ String[] titles;
+
+ public TransitionsArrayAdapter(Context context, int textViewResourceId,
+ String[] objects) {
+ super(context, textViewResourceId, objects);
+
+ mContext = context;
+ titles = objects;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ LayoutInflater inflater = (LayoutInflater) mContext
+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ convertView = inflater.inflate(R.layout.settings_pane_list_item,
+ parent, false);
+ TextView textView = (TextView) convertView
+ .findViewById(R.id.item_name);
+ textView.setText(titles[position]);
+ // Set Selected State
+ if (position == mCurrentPosition) {
+ mCurrentSelection = convertView;
+ setSelected(mCurrentSelection);
+ }
+
+ convertView.setOnClickListener(mSettingsItemListener);
+ convertView.setTag(position);
+ return convertView;
+ }
+ }
+}
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 282e47516..cbc978585 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -21,14 +21,21 @@ import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
-import android.graphics.*;
+import android.graphics.Bitmap;
+import android.graphics.BlurMaskFilter;
+import android.graphics.Canvas;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.PaintFlagsDrawFilter;
+import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.PaintDrawable;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
-import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
@@ -36,7 +43,7 @@ import java.util.ArrayList;
/**
* Various utilities shared amongst the Launcher's classes.
*/
-final class Utilities {
+public final class Utilities {
private static final String TAG = "Launcher.Utilities";
private static int sIconWidth = -1;
@@ -51,8 +58,6 @@ final class Utilities {
private static final Rect sOldBounds = new Rect();
private static final Canvas sCanvas = new Canvas();
- private static Typeface sTypeface;
-
static {
sCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG,
Paint.FILTER_BITMAP_FLAG));
@@ -60,6 +65,12 @@ final class Utilities {
static int sColors[] = { 0xffff0000, 0xff00ff00, 0xff0000ff };
static int sColorIndex = 0;
+
+ // To turn on these properties, type
+ // adb shell setprop log.tag.PROPERTY_NAME [VERBOSE | SUPPRESS]
+ static final String FORCE_ENABLE_ROTATION_PROPERTY = "launcher_force_rotate";
+ public static boolean sForceEnableRotation = isPropertyEnabled(FORCE_ENABLE_ROTATION_PROPERTY);
+
/**
* Returns a FastBitmapDrawable with the icon, accurately sized.
*/
@@ -77,6 +88,16 @@ final class Utilities {
icon.setBounds(0, 0, sIconTextureWidth, sIconTextureHeight);
}
+ private static boolean isPropertyEnabled(String propertyName) {
+ return Log.isLoggable(propertyName, Log.VERBOSE);
+ }
+
+ public static boolean isRotationEnabled(Context c) {
+ boolean enableRotation = sForceEnableRotation ||
+ c.getResources().getBoolean(R.bool.allow_rotation);
+ return enableRotation;
+ }
+
/**
* Returns a bitmap suitable for the all apps view. Used to convert pre-ICS
* icon bitmaps that are stored in the database (which were 74x74 pixels at hdpi size)
@@ -103,15 +124,10 @@ final class Utilities {
}
}
- static Bitmap createIconBitmap(Drawable icon, Context context) {
- return createIconBitmap(icon, context, null, null, null, 1f);
- }
-
/**
* Returns a bitmap suitable for the all apps view.
*/
- static Bitmap createIconBitmap(Drawable icon, Context context, Drawable iconBack,
- Drawable iconMask, Drawable iconUpon, float scale) {
+ public static Bitmap createIconBitmap(Drawable icon, Context context) {
synchronized (sCanvas) { // we share the statics :-(
if (sIconWidth == -1) {
initStatics(context);
@@ -148,7 +164,7 @@ final class Utilities {
int textureWidth = sIconTextureWidth;
int textureHeight = sIconTextureHeight;
- Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight,
+ final Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight,
Bitmap.Config.ARGB_8888);
final Canvas canvas = sCanvas;
canvas.setBitmap(bitmap);
@@ -169,29 +185,7 @@ final class Utilities {
sOldBounds.set(icon.getBounds());
icon.setBounds(left, top, left+width, top+height);
- canvas.save();
- canvas.scale(scale, scale, width / 2, height/2);
icon.draw(canvas);
- canvas.restore();
- if (iconMask != null) {
- iconMask.setBounds(icon.getBounds());
- ((BitmapDrawable) iconMask).getPaint().setXfermode(
- new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
- iconMask.draw(canvas);
- }
- if (iconBack != null) {
- canvas.setBitmap(null);
- Bitmap finalBitmap = Bitmap.createBitmap(textureWidth, textureHeight,
- Bitmap.Config.ARGB_8888);
- canvas.setBitmap(finalBitmap);
- iconBack.setBounds(icon.getBounds());
- iconBack.draw(canvas);
- canvas.drawBitmap(bitmap, null, icon.getBounds(), null);
- bitmap = finalBitmap;
- }
- if (iconUpon != null) {
- iconUpon.draw(canvas);
- }
icon.setBounds(sOldBounds);
canvas.setBitmap(null);
@@ -224,27 +218,6 @@ final class Utilities {
}
/**
- * Generates the default icon typeface for use in icons.
- *
- * @param familyName May be null. The name of the font family.
- * @param style The style (normal, bold, italic) of the typeface. e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC
- */
- static void generateTypeface(String familyName, int style) {
- sTypeface = Typeface.create(familyName, style);
- }
-
- /**
- * Applies the default icon typeface to a textview.
- *
- * @param textView View to apply typeface to.
- */
- static void applyTypeface(TextView textView) {
- if (sTypeface != null) {
- textView.setTypeface(sTypeface);
- }
- }
-
- /**
* Given a coordinate relative to the descendant, find the coordinate in a parent view's
* coordinates.
*
diff --git a/src/com/android/gallery3d/glrenderer/GLPaint.java b/src/com/android/launcher3/WallpaperChangedReceiver.java
index 16b220690..2d5612f12 100644
--- a/src/com/android/gallery3d/glrenderer/GLPaint.java
+++ b/src/com/android/launcher3/WallpaperChangedReceiver.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010 The Android Open Source Project
+ * 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.
@@ -14,28 +14,16 @@
* limitations under the License.
*/
-package com.android.gallery3d.glrenderer;
+package com.android.launcher3;
-import junit.framework.Assert;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
-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) {
- Assert.assertTrue(width >= 0);
- mLineWidth = width;
- }
-
- public float getLineWidth() {
- return mLineWidth;
+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/WallpaperCropActivity.java b/src/com/android/launcher3/WallpaperCropActivity.java
deleted file mode 100644
index 5796a5ddd..000000000
--- a/src/com/android/launcher3/WallpaperCropActivity.java
+++ /dev/null
@@ -1,793 +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.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;
-import android.graphics.Bitmap.CompressFormat;
-import android.graphics.BitmapFactory;
-import android.graphics.BitmapRegionDecoder;
-import android.graphics.Canvas;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.util.FloatMath;
-import android.util.Log;
-import android.view.Display;
-import android.view.View;
-import android.view.WindowManager;
-
-import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.exif.ExifInterface;
-import com.android.photos.BitmapRegionTileSource;
-
-import java.io.BufferedInputStream;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-
-public class WallpaperCropActivity extends Activity {
- private static final String LOGTAG = "Launcher3.CropActivity";
-
- protected static final String WALLPAPER_WIDTH_KEY = "wallpaper.width";
- protected static final String WALLPAPER_HEIGHT_KEY = "wallpaper.height";
- protected static final int DEFAULT_COMPRESS_QUALITY = 90;
- /**
- * The maximum bitmap size we allow to be returned through the intent.
- * Intents have a maximum of 1MB in total size. However, the Bitmap seems to
- * have some overhead to hit so that we go way below the limit here to make
- * sure the intent stays below 1MB.We should consider just returning a byte
- * array instead of a Bitmap instance to avoid overhead.
- */
- public static final int MAX_BMAP_IN_INTENT = 750000;
- protected static final float WALLPAPER_SCREENS_SPAN = 2f;
-
- protected CropView mCropView;
- protected Uri mUri;
-
- public static abstract class WallpaperTileInfo {
- protected View mView;
- public void setView(View v) {
- mView = v;
- }
- public void onClick(WallpaperCropActivity a) {}
- public void onSave(WallpaperCropActivity a) {}
- public void onDelete(WallpaperCropActivity a) {}
- public boolean isSelectable() { return false; }
- public boolean isNamelessWallpaper() { return false; }
- public void onIndexUpdated(CharSequence label) {
- if (isNamelessWallpaper()) {
- mView.setContentDescription(label);
- }
- }
- }
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- init();
- if (!enableRotation()) {
- setRequestedOrientation(Configuration.ORIENTATION_PORTRAIT);
- }
- }
-
- protected void init() {
- setContentView(R.layout.wallpaper_cropper);
-
- mCropView = (CropView) findViewById(R.id.cropView);
-
- Intent cropIntent = getIntent();
- final Uri imageUri = cropIntent.getData();
-
- if (imageUri == null) {
- Log.e(LOGTAG, "No URI passed in intent, exiting WallpaperCropActivity");
- finish();
- return;
- }
-
- int rotation = getRotationFromExif(this, imageUri);
- mCropView.setTileSource(new BitmapRegionTileSource(this, imageUri, 1024, rotation), null);
- mCropView.setTouchEnabled(true);
- // Action bar
- // Show the custom action bar view
- final ActionBar actionBar = getActionBar();
- actionBar.setCustomView(R.layout.actionbar_set_wallpaper);
- actionBar.getCustomView().setOnClickListener(
- new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- boolean finishActivityWhenDone = true;
- cropImageAndSetWallpaper(imageUri, null, finishActivityWhenDone);
- }
- });
- }
-
- public boolean enableRotation() {
- return getResources().getBoolean(R.bool.allow_rotation);
- }
-
- public static String getSharedPreferencesKey() {
- return WallpaperCropActivity.class.getName();
- }
-
- // As a ratio of screen height, the total distance we want the parallax effect to span
- // horizontally
- protected 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
- // At an aspect ratio of 10/16, the wallpaper parallax effect should span 1.2 * screen width
- // We will use these two data points to extrapolate how much the wallpaper parallax effect
- // to span (ie travel) at any aspect ratio:
-
- final float ASPECT_RATIO_LANDSCAPE = 16/10f;
- final float ASPECT_RATIO_PORTRAIT = 10/16f;
- final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE = 1.5f;
- final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT = 1.2f;
-
- // To find out the desired width at different aspect ratios, we use the following two
- // formulas, where the coefficient on x is the aspect ratio (width/height):
- // (16/10)x + y = 1.5
- // (10/16)x + y = 1.2
- // We solve for x and y and end up with a final formula:
- final float x =
- (WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE - WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT) /
- (ASPECT_RATIO_LANDSCAPE - ASPECT_RATIO_PORTRAIT);
- final float y = WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT - x * ASPECT_RATIO_PORTRAIT;
- return x * aspectRatio + y;
- }
-
- static protected Point getDefaultWallpaperSize(Resources res, WindowManager windowManager) {
- 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);
- }
-
- // We need to ensure that there is enough extra space in the wallpaper
- // for the intended
- // parallax effects
- final int defaultWidth, defaultHeight;
- if (isScreenLarge(res)) {
- defaultWidth = (int) (maxDim * wallpaperTravelToScreenWidthRatio(maxDim, minDim));
- defaultHeight = maxDim;
- } else {
- defaultWidth = Math.max((int) (minDim * WALLPAPER_SCREENS_SPAN), maxDim);
- defaultHeight = maxDim;
- }
- return new Point(defaultWidth, defaultHeight);
- }
-
- public static int getRotationFromExif(String path) {
- return getRotationFromExifHelper(path, null, 0, null, null);
- }
-
- public static int getRotationFromExif(Context context, Uri uri) {
- return getRotationFromExifHelper(null, null, 0, context, uri);
- }
-
- public static int getRotationFromExif(Resources res, int resId) {
- return getRotationFromExifHelper(null, res, resId, null, null);
- }
-
- private static int getRotationFromExifHelper(
- String path, Resources res, int resId, Context context, Uri uri) {
- ExifInterface ei = new ExifInterface();
- try {
- if (path != null) {
- ei.readExif(path);
- } else if (uri != null) {
- InputStream is = context.getContentResolver().openInputStream(uri);
- BufferedInputStream bis = new BufferedInputStream(is);
- ei.readExif(bis);
- } else {
- InputStream is = res.openRawResource(resId);
- BufferedInputStream 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(LOGTAG, "Getting exif data failed", e);
- } catch (NullPointerException e) {
- Log.w(LOGTAG, "Getting exif data failed", e);
- }
- return 0;
- }
-
- protected void setWallpaper(String filePath, final boolean finishActivityWhenDone) {
- int rotation = getRotationFromExif(filePath);
- BitmapCropTask cropTask = new BitmapCropTask(
- this, filePath, null, rotation, 0, 0, true, false, null);
- final Point bounds = cropTask.getImageBounds();
- Runnable onEndCrop = new Runnable() {
- public void run() {
- updateWallpaperDimensions(bounds.x, bounds.y);
- if (finishActivityWhenDone) {
- setResult(Activity.RESULT_OK);
- finish();
- }
- }
- };
- cropTask.setOnEndRunnable(onEndCrop);
- cropTask.setNoCrop(true);
- cropTask.execute();
- }
-
- protected void cropImageAndSetWallpaper(
- Resources res, int resId, final boolean finishActivityWhenDone) {
- // crop this image and scale it down to the default wallpaper size for
- // this device
- int rotation = getRotationFromExif(res, resId);
- Point inSize = mCropView.getSourceDimensions();
- Point outSize = getDefaultWallpaperSize(getResources(),
- getWindowManager());
- RectF crop = getMaxCropRect(
- inSize.x, inSize.y, outSize.x, outSize.y, false);
- Runnable onEndCrop = new Runnable() {
- public void run() {
- // Passing 0, 0 will cause launcher to revert to using the
- // default wallpaper size
- updateWallpaperDimensions(0, 0);
- if (finishActivityWhenDone) {
- setResult(Activity.RESULT_OK);
- finish();
- }
- }
- };
- BitmapCropTask cropTask = new BitmapCropTask(this, res, resId,
- crop, rotation, outSize.x, outSize.y, true, false, onEndCrop);
- cropTask.execute();
- }
-
- protected static boolean isScreenLarge(Resources res) {
- Configuration config = res.getConfiguration();
- return config.smallestScreenWidthDp >= 720;
- }
-
- protected void cropImageAndSetWallpaper(Uri uri,
- OnBitmapCroppedHandler onBitmapCroppedHandler, final boolean finishActivityWhenDone) {
- // Get the crop
- boolean ltr = mCropView.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
-
- Point minDims = new Point();
- Point maxDims = new Point();
- Display d = getWindowManager().getDefaultDisplay();
- d.getCurrentSizeRange(minDims, maxDims);
-
- Point displaySize = new Point();
- d.getSize(displaySize);
-
- int maxDim = Math.max(maxDims.x, maxDims.y);
- final int minDim = Math.min(minDims.x, minDims.y);
- int defaultWallpaperWidth;
- if (isScreenLarge(getResources())) {
- defaultWallpaperWidth = (int) (maxDim *
- wallpaperTravelToScreenWidthRatio(maxDim, minDim));
- } else {
- defaultWallpaperWidth = Math.max((int)
- (minDim * WALLPAPER_SCREENS_SPAN), maxDim);
- }
-
- boolean isPortrait = displaySize.x < displaySize.y;
- int portraitHeight;
- if (isPortrait) {
- portraitHeight = mCropView.getHeight();
- } else {
- // TODO: how to actually get the proper portrait height?
- // This is not quite right:
- portraitHeight = Math.max(maxDims.x, maxDims.y);
- }
- if (android.os.Build.VERSION.SDK_INT >=
- android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
- Point realSize = new Point();
- d.getRealSize(realSize);
- portraitHeight = Math.max(realSize.x, realSize.y);
- }
- // Get the crop
- RectF cropRect = mCropView.getCrop();
- int cropRotation = mCropView.getImageRotation();
- float cropScale = mCropView.getWidth() / (float) cropRect.width();
-
- Point inSize = mCropView.getSourceDimensions();
- Matrix rotateMatrix = new Matrix();
- rotateMatrix.setRotate(cropRotation);
- float[] rotatedInSize = new float[] { inSize.x, inSize.y };
- rotateMatrix.mapPoints(rotatedInSize);
- rotatedInSize[0] = Math.abs(rotatedInSize[0]);
- rotatedInSize[1] = Math.abs(rotatedInSize[1]);
-
- // ADJUST CROP WIDTH
- // Extend the crop all the way to the right, for parallax
- // (or all the way to the left, in RTL)
- float extraSpace = ltr ? rotatedInSize[0] - cropRect.right : cropRect.left;
- // Cap the amount of extra width
- float maxExtraSpace = defaultWallpaperWidth / cropScale - cropRect.width();
- extraSpace = Math.min(extraSpace, maxExtraSpace);
-
- if (ltr) {
- cropRect.right += extraSpace;
- } else {
- cropRect.left -= extraSpace;
- }
-
- // ADJUST CROP HEIGHT
- if (isPortrait) {
- cropRect.bottom = cropRect.top + portraitHeight / cropScale;
- } else { // LANDSCAPE
- float extraPortraitHeight =
- portraitHeight / cropScale - cropRect.height();
- float expandHeight =
- Math.min(Math.min(rotatedInSize[1] - cropRect.bottom, cropRect.top),
- extraPortraitHeight / 2);
- cropRect.top -= expandHeight;
- cropRect.bottom += expandHeight;
- }
- final int outWidth = (int) Math.round(cropRect.width() * cropScale);
- final int outHeight = (int) Math.round(cropRect.height() * cropScale);
-
- Runnable onEndCrop = new Runnable() {
- public void run() {
- updateWallpaperDimensions(outWidth, outHeight);
- if (finishActivityWhenDone) {
- setResult(Activity.RESULT_OK);
- finish();
- }
- }
- };
- BitmapCropTask cropTask = new BitmapCropTask(this, uri,
- cropRect, cropRotation, outWidth, outHeight, true, false, onEndCrop);
- if (onBitmapCroppedHandler != null) {
- cropTask.setOnBitmapCropped(onBitmapCroppedHandler);
- }
- cropTask.execute();
- }
-
- public interface OnBitmapCroppedHandler {
- public void onBitmapCropped(byte[] imageBytes);
- }
-
- protected static class BitmapCropTask extends AsyncTask<Void, Void, Boolean> {
- Uri mInUri = null;
- Context mContext;
- String mInFilePath;
- byte[] mInImageBytes;
- int mInResId = 0;
- InputStream mInStream;
- RectF mCropBounds = null;
- int mOutWidth, mOutHeight;
- int mRotation;
- String mOutputFormat = "jpg"; // for now
- boolean mSetWallpaper;
- boolean mSaveCroppedBitmap;
- Bitmap mCroppedBitmap;
- Runnable mOnEndRunnable;
- Resources mResources;
- OnBitmapCroppedHandler mOnBitmapCroppedHandler;
- boolean mNoCrop;
-
- public BitmapCropTask(Context c, String filePath,
- RectF cropBounds, int rotation, int outWidth, int outHeight,
- boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
- mContext = c;
- mInFilePath = filePath;
- init(cropBounds, rotation,
- outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
- }
-
- public BitmapCropTask(byte[] imageBytes,
- RectF cropBounds, int rotation, int outWidth, int outHeight,
- boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
- mInImageBytes = imageBytes;
- init(cropBounds, rotation,
- outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
- }
-
- public BitmapCropTask(Context c, Uri inUri,
- RectF cropBounds, int rotation, int outWidth, int outHeight,
- boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
- mContext = c;
- mInUri = inUri;
- init(cropBounds, rotation,
- outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
- }
-
- public BitmapCropTask(Context c, Resources res, int inResId,
- RectF cropBounds, int rotation, int outWidth, int outHeight,
- boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
- mContext = c;
- mInResId = inResId;
- mResources = res;
- init(cropBounds, rotation,
- outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
- }
-
- private void init(RectF cropBounds, int rotation, int outWidth, int outHeight,
- boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
- mCropBounds = cropBounds;
- mRotation = rotation;
- mOutWidth = outWidth;
- mOutHeight = outHeight;
- mSetWallpaper = setWallpaper;
- mSaveCroppedBitmap = saveCroppedBitmap;
- mOnEndRunnable = onEndRunnable;
- }
-
- public void setOnBitmapCropped(OnBitmapCroppedHandler handler) {
- mOnBitmapCroppedHandler = handler;
- }
-
- public void setNoCrop(boolean value) {
- mNoCrop = value;
- }
-
- public void setOnEndRunnable(Runnable onEndRunnable) {
- mOnEndRunnable = onEndRunnable;
- }
-
- // Helper to setup input stream
- private void regenerateInputStream() {
- if (mInUri == null && mInResId == 0 && mInFilePath == null && mInImageBytes == null) {
- Log.w(LOGTAG, "cannot read original file, no input URI, resource ID, or " +
- "image byte array given");
- } else {
- Utils.closeSilently(mInStream);
- try {
- if (mInUri != null) {
- mInStream = new BufferedInputStream(
- mContext.getContentResolver().openInputStream(mInUri));
- } else if (mInFilePath != null) {
- mInStream = mContext.openFileInput(mInFilePath);
- } else if (mInImageBytes != null) {
- mInStream = new BufferedInputStream(
- new ByteArrayInputStream(mInImageBytes));
- } else {
- mInStream = new BufferedInputStream(
- mResources.openRawResource(mInResId));
- }
- } catch (FileNotFoundException e) {
- Log.w(LOGTAG, "cannot read file: " + mInUri.toString(), e);
- }
- }
- }
-
- public Point getImageBounds() {
- regenerateInputStream();
- if (mInStream != null) {
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inJustDecodeBounds = true;
- BitmapFactory.decodeStream(mInStream, null, options);
- if (options.outWidth != 0 && options.outHeight != 0) {
- return new Point(options.outWidth, options.outHeight);
- }
- }
- return null;
- }
-
- public void setCropBounds(RectF cropBounds) {
- mCropBounds = cropBounds;
- }
-
- public Bitmap getCroppedBitmap() {
- return mCroppedBitmap;
- }
- public boolean cropBitmap() {
- boolean failure = false;
-
- regenerateInputStream();
-
- WallpaperManager wallpaperManager = null;
- if (mSetWallpaper) {
- wallpaperManager = WallpaperManager.getInstance(mContext.getApplicationContext());
- }
- if (mSetWallpaper && mNoCrop && mInStream != null) {
- try {
- wallpaperManager.setStream(mInStream);
- } catch (IOException e) {
- Log.w(LOGTAG, "cannot write stream to wallpaper", e);
- failure = true;
- }
- return !failure;
- }
- if (mInStream != null) {
- // Find crop bounds (scaled to original image size)
- Rect roundedTrueCrop = new Rect();
- Matrix rotateMatrix = new Matrix();
- Matrix inverseRotateMatrix = new Matrix();
- if (mRotation > 0) {
- rotateMatrix.setRotate(mRotation);
- inverseRotateMatrix.setRotate(-mRotation);
-
- mCropBounds.roundOut(roundedTrueCrop);
- mCropBounds = new RectF(roundedTrueCrop);
-
- Point bounds = getImageBounds();
-
- float[] rotatedBounds = new float[] { bounds.x, bounds.y };
- rotateMatrix.mapPoints(rotatedBounds);
- rotatedBounds[0] = Math.abs(rotatedBounds[0]);
- rotatedBounds[1] = Math.abs(rotatedBounds[1]);
-
- mCropBounds.offset(-rotatedBounds[0]/2, -rotatedBounds[1]/2);
- inverseRotateMatrix.mapRect(mCropBounds);
- mCropBounds.offset(bounds.x/2, bounds.y/2);
-
- regenerateInputStream();
- }
-
- mCropBounds.roundOut(roundedTrueCrop);
-
- if (roundedTrueCrop.width() <= 0 || roundedTrueCrop.height() <= 0) {
- Log.w(LOGTAG, "crop has bad values for full size image");
- failure = true;
- return false;
- }
-
- // See how much we're reducing the size of the image
- int scaleDownSampleSize = Math.min(roundedTrueCrop.width() / mOutWidth,
- roundedTrueCrop.height() / mOutHeight);
-
- // Attempt to open a region decoder
- BitmapRegionDecoder decoder = null;
- try {
- decoder = BitmapRegionDecoder.newInstance(mInStream, true);
- } catch (IOException e) {
- Log.w(LOGTAG, "cannot open region decoder for file: " + mInUri.toString(), e);
- }
-
- Bitmap crop = null;
- if (decoder != null) {
- // Do region decoding to get crop bitmap
- BitmapFactory.Options options = new BitmapFactory.Options();
- if (scaleDownSampleSize > 1) {
- options.inSampleSize = scaleDownSampleSize;
- }
- crop = decoder.decodeRegion(roundedTrueCrop, options);
- decoder.recycle();
- }
-
- if (crop == null) {
- // BitmapRegionDecoder has failed, try to crop in-memory
- regenerateInputStream();
- Bitmap fullSize = null;
- if (mInStream != null) {
- BitmapFactory.Options options = new BitmapFactory.Options();
- if (scaleDownSampleSize > 1) {
- options.inSampleSize = scaleDownSampleSize;
- }
- fullSize = BitmapFactory.decodeStream(mInStream, null, options);
- }
- if (fullSize != null) {
- mCropBounds.left /= scaleDownSampleSize;
- mCropBounds.top /= scaleDownSampleSize;
- mCropBounds.bottom /= scaleDownSampleSize;
- mCropBounds.right /= scaleDownSampleSize;
- mCropBounds.roundOut(roundedTrueCrop);
-
- crop = Bitmap.createBitmap(fullSize, roundedTrueCrop.left,
- roundedTrueCrop.top, roundedTrueCrop.width(),
- roundedTrueCrop.height());
- }
- }
-
- if (crop == null) {
- Log.w(LOGTAG, "cannot decode file: " + mInUri.toString());
- failure = true;
- return false;
- }
- if (mOutWidth > 0 && mOutHeight > 0 || mRotation > 0) {
- float[] dimsAfter = new float[] { crop.getWidth(), crop.getHeight() };
- rotateMatrix.mapPoints(dimsAfter);
- dimsAfter[0] = Math.abs(dimsAfter[0]);
- dimsAfter[1] = Math.abs(dimsAfter[1]);
-
- if (!(mOutWidth > 0 && mOutHeight > 0)) {
- mOutWidth = Math.round(dimsAfter[0]);
- mOutHeight = Math.round(dimsAfter[1]);
- }
-
- RectF cropRect = new RectF(0, 0, dimsAfter[0], dimsAfter[1]);
- RectF returnRect = new RectF(0, 0, mOutWidth, mOutHeight);
-
- Matrix m = new Matrix();
- if (mRotation == 0) {
- m.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL);
- } else {
- Matrix m1 = new Matrix();
- m1.setTranslate(-crop.getWidth() / 2f, -crop.getHeight() / 2f);
- Matrix m2 = new Matrix();
- m2.setRotate(mRotation);
- Matrix m3 = new Matrix();
- m3.setTranslate(dimsAfter[0] / 2f, dimsAfter[1] / 2f);
- Matrix m4 = new Matrix();
- m4.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL);
-
- Matrix c1 = new Matrix();
- c1.setConcat(m2, m1);
- Matrix c2 = new Matrix();
- c2.setConcat(m4, m3);
- m.setConcat(c2, c1);
- }
-
- Bitmap tmp = Bitmap.createBitmap((int) returnRect.width(),
- (int) returnRect.height(), Bitmap.Config.ARGB_8888);
- if (tmp != null) {
- Canvas c = new Canvas(tmp);
- Paint p = new Paint();
- p.setFilterBitmap(true);
- c.drawBitmap(crop, m, p);
- crop = tmp;
- }
- }
-
- if (mSaveCroppedBitmap) {
- mCroppedBitmap = crop;
- }
-
- // Get output compression format
- CompressFormat cf =
- convertExtensionToCompressFormat(getFileExtension(mOutputFormat));
-
- // Compress to byte array
- ByteArrayOutputStream tmpOut = new ByteArrayOutputStream(2048);
- if (crop.compress(cf, DEFAULT_COMPRESS_QUALITY, tmpOut)) {
- // If we need to set to the wallpaper, set it
- if (mSetWallpaper && wallpaperManager != null) {
- try {
- byte[] outByteArray = tmpOut.toByteArray();
- wallpaperManager.setStream(new ByteArrayInputStream(outByteArray));
- if (mOnBitmapCroppedHandler != null) {
- mOnBitmapCroppedHandler.onBitmapCropped(outByteArray);
- }
- } catch (IOException e) {
- Log.w(LOGTAG, "cannot write stream to wallpaper", e);
- failure = true;
- }
- }
- } else {
- Log.w(LOGTAG, "cannot compress bitmap");
- failure = true;
- }
- }
- return !failure; // True if any of the operations failed
- }
-
- @Override
- protected Boolean doInBackground(Void... params) {
- return cropBitmap();
- }
-
- @Override
- protected void onPostExecute(Boolean result) {
- if (mOnEndRunnable != null) {
- mOnEndRunnable.run();
- }
- }
- }
-
- protected void updateWallpaperDimensions(int width, int height) {
- String spKey = getSharedPreferencesKey();
- SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_PRIVATE);
- 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();
-
- suggestWallpaperDimension(getResources(),
- sp, getWindowManager(), WallpaperManager.getInstance(this));
- }
-
- static public void suggestWallpaperDimension(Resources res,
- final SharedPreferences sharedPrefs,
- WindowManager windowManager,
- final WallpaperManager wallpaperManager) {
- final Point defaultWallpaperSize = getDefaultWallpaperSize(res, windowManager);
-
- new Thread("suggestWallpaperDimension") {
- public void run() {
- // If we have saved a wallpaper width/height, use that instead
- int savedWidth = sharedPrefs.getInt(WALLPAPER_WIDTH_KEY, defaultWallpaperSize.x);
- int savedHeight = sharedPrefs.getInt(WALLPAPER_HEIGHT_KEY, defaultWallpaperSize.y);
- wallpaperManager.suggestDesiredDimensions(savedWidth, savedHeight);
- }
- }.start();
- }
-
- protected static RectF getMaxCropRect(
- int inWidth, int inHeight, int outWidth, int outHeight, boolean leftAligned) {
- RectF cropRect = new RectF();
- // Get a crop rect that will fit this
- if (inWidth / (float) inHeight > outWidth / (float) outHeight) {
- cropRect.top = 0;
- cropRect.bottom = inHeight;
- cropRect.left = (inWidth - (outWidth / (float) outHeight) * inHeight) / 2;
- cropRect.right = inWidth - cropRect.left;
- if (leftAligned) {
- cropRect.right -= cropRect.left;
- cropRect.left = 0;
- }
- } else {
- cropRect.left = 0;
- cropRect.right = inWidth;
- cropRect.top = (inHeight - (outHeight / (float) outWidth) * inWidth) / 2;
- cropRect.bottom = inHeight - cropRect.top;
- }
- return cropRect;
- }
-
- protected static CompressFormat convertExtensionToCompressFormat(String extension) {
- return extension.equals("png") ? CompressFormat.PNG : CompressFormat.JPEG;
- }
-
- protected static String getFileExtension(String requestFormat) {
- String outputFormat = (requestFormat == null)
- ? "jpg"
- : requestFormat;
- outputFormat = outputFormat.toLowerCase();
- return (outputFormat.equals("png") || outputFormat.equals("gif"))
- ? "png" // We don't support gif compression.
- : "jpg";
- }
-
- protected void setWallpaperStripYOffset(int bottom) {
- //
- }
-
- protected SavedWallpaperImages getSavedImages() {
- // for subclasses
- throw new UnsupportedOperationException("Not implemented for WallpaperCropActivity");
- }
-
- protected CropView getCropView() {
- // for subclasses
- throw new UnsupportedOperationException("Not implemented for WallpaperCropActivity");
- }
-
- protected void onLiveWallpaperPickerLaunch() {
- // for subclasses
- throw new UnsupportedOperationException("Not implemented for WallpaperCropActivity");
- }
-}
diff --git a/src/com/android/launcher3/WallpaperPickerActivity.java b/src/com/android/launcher3/WallpaperPickerActivity.java
deleted file mode 100644
index f8e5233f7..000000000
--- a/src/com/android/launcher3/WallpaperPickerActivity.java
+++ /dev/null
@@ -1,884 +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.animation.Animator;
-import android.animation.LayoutTransition;
-import android.app.ActionBar;
-import android.app.Activity;
-import android.app.WallpaperInfo;
-import android.app.WallpaperManager;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.database.DataSetObserver;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Matrix;
-import android.graphics.Point;
-import android.graphics.PorterDuff;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.LevelListDrawable;
-import android.net.Uri;
-import android.os.Bundle;
-import android.provider.MediaStore;
-import android.util.Log;
-import android.util.Pair;
-import android.view.ActionMode;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
-import android.view.ViewTreeObserver.OnGlobalLayoutListener;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.animation.AccelerateInterpolator;
-import android.view.animation.DecelerateInterpolator;
-import android.widget.BaseAdapter;
-import android.widget.FrameLayout;
-import android.widget.HorizontalScrollView;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.ListAdapter;
-
-import com.android.gallery3d.exif.ExifInterface;
-import com.android.photos.BitmapRegionTileSource;
-import com.android.launcher3.settings.SettingsProvider;
-
-import java.io.BufferedInputStream;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-
-public class WallpaperPickerActivity extends WallpaperCropActivity {
- static final String TAG = "Launcher.WallpaperPickerActivity";
-
- public static final int IMAGE_PICK = 5;
- public static final int PICK_WALLPAPER_THIRD_PARTY_ACTIVITY = 6;
- public static final int PICK_LIVE_WALLPAPER = 7;
- private static final String TEMP_WALLPAPER_TILES = "TEMP_WALLPAPER_TILES";
-
- private static final int MENU_WALLPAPER_SCROLL = 0;
-
-
- private View mSelectedThumb;
- private boolean mIgnoreNextTap;
- private OnClickListener mThumbnailOnClickListener;
-
- private LinearLayout mWallpapersView;
- private View mWallpaperStrip;
-
- private ActionMode.Callback mActionModeCallback;
- private ActionMode mActionMode;
-
- private View.OnLongClickListener mLongClickListener;
-
- ArrayList<Uri> mTempWallpaperTiles = new ArrayList<Uri>();
- private SavedWallpaperImages mSavedImages;
- private WallpaperInfo mLiveWallpaperInfoOnPickerLaunch;
-
- public static class PickImageInfo extends WallpaperTileInfo {
- @Override
- public void onClick(WallpaperCropActivity a) {
- Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
- intent.setType("image/*");
- Utilities.startActivityForResultSafely(((WallpaperPickerActivity)a), intent, IMAGE_PICK);
- }
- }
-
- public static class UriWallpaperInfo extends WallpaperTileInfo {
- private Uri mUri;
- public UriWallpaperInfo(Uri uri) {
- mUri = uri;
- }
- @Override
- public void onClick(WallpaperCropActivity a) {
- CropView v = a.getCropView();
- int rotation = WallpaperCropActivity.getRotationFromExif(a, mUri);
- v.setTileSource(new BitmapRegionTileSource(a, mUri, 1024, rotation), null);
- v.setTouchEnabled(true);
- }
- @Override
- public void onSave(final WallpaperCropActivity a) {
- boolean finishActivityWhenDone = true;
- OnBitmapCroppedHandler h = new OnBitmapCroppedHandler() {
- public void onBitmapCropped(byte[] imageBytes) {
- Point thumbSize = getDefaultThumbnailSize(a.getResources());
- // rotation is set to 0 since imageBytes has already been correctly rotated
- Bitmap thumb = createThumbnail(
- thumbSize, null, null, imageBytes, null, 0, 0, true);
- ((WallpaperPickerActivity)a).getSavedImages().writeImage(thumb, imageBytes);
- }
- };
- ((WallpaperPickerActivity)a).cropImageAndSetWallpaper(mUri, h, finishActivityWhenDone);
- }
- @Override
- public boolean isSelectable() {
- return true;
- }
- @Override
- public boolean isNamelessWallpaper() {
- return true;
- }
- }
-
- public static class ResourceWallpaperInfo extends WallpaperTileInfo {
- private Resources mResources;
- private int mResId;
- private Drawable mThumb;
-
- public ResourceWallpaperInfo(Resources res, int resId, Drawable thumb) {
- mResources = res;
- mResId = resId;
- mThumb = thumb;
- }
- @Override
- public void onClick(WallpaperCropActivity a) {
- int rotation = WallpaperCropActivity.getRotationFromExif(mResources, mResId);
- BitmapRegionTileSource source = new BitmapRegionTileSource(
- mResources, a, mResId, 1024, rotation);
- CropView v = a.getCropView();
- v.setTileSource(source, null);
- Point wallpaperSize = WallpaperCropActivity.getDefaultWallpaperSize(
- a.getResources(), a.getWindowManager());
- RectF crop = WallpaperCropActivity.getMaxCropRect(
- source.getImageWidth(), source.getImageHeight(),
- wallpaperSize.x, wallpaperSize.y, false);
- v.setScale(wallpaperSize.x / crop.width());
- v.setTouchEnabled(false);
- }
- @Override
- public void onSave(WallpaperCropActivity a) {
- boolean finishActivityWhenDone = true;
- ((WallpaperPickerActivity)a).cropImageAndSetWallpaper(mResources, mResId, finishActivityWhenDone);
- }
- @Override
- public boolean isSelectable() {
- return true;
- }
- @Override
- public boolean isNamelessWallpaper() {
- return true;
- }
- }
-
- @Override
- protected void setWallpaperStripYOffset(int offset) {
- mWallpaperStrip.setPadding(0, 0, 0, offset);
- }
-
- // called by onCreate; this is subclassed to overwrite WallpaperCropActivity
- protected void init() {
- setContentView(R.layout.wallpaper_picker);
-
- mCropView = (CropView) findViewById(R.id.cropView);
- mWallpaperStrip = findViewById(R.id.wallpaper_strip);
- mCropView.setTouchCallback(new CropView.TouchCallback() {
- LauncherViewPropertyAnimator mAnim;
- @Override
- public void onTouchDown() {
- if (mAnim != null) {
- mAnim.cancel();
- }
- if (mWallpaperStrip.getAlpha() == 1f) {
- mIgnoreNextTap = true;
- }
- mAnim = new LauncherViewPropertyAnimator(mWallpaperStrip);
- mAnim.alpha(0f)
- .setDuration(150)
- .addListener(new Animator.AnimatorListener() {
- public void onAnimationStart(Animator animator) { }
- public void onAnimationEnd(Animator animator) {
- mWallpaperStrip.setVisibility(View.INVISIBLE);
- }
- public void onAnimationCancel(Animator animator) { }
- public void onAnimationRepeat(Animator animator) { }
- });
- mAnim.setInterpolator(new AccelerateInterpolator(0.75f));
- mAnim.start();
- }
- @Override
- public void onTouchUp() {
- mIgnoreNextTap = false;
- }
- @Override
- public void onTap() {
- boolean ignoreTap = mIgnoreNextTap;
- mIgnoreNextTap = false;
- if (!ignoreTap) {
- if (mAnim != null) {
- mAnim.cancel();
- }
- mWallpaperStrip.setVisibility(View.VISIBLE);
- mAnim = new LauncherViewPropertyAnimator(mWallpaperStrip);
- mAnim.alpha(1f)
- .setDuration(150)
- .setInterpolator(new DecelerateInterpolator(0.75f));
- mAnim.start();
- }
- }
- });
-
- mThumbnailOnClickListener = new OnClickListener() {
- public void onClick(View v) {
- if (mActionMode != null) {
- // When CAB is up, clicking toggles the item instead
- if (v.isLongClickable()) {
- mLongClickListener.onLongClick(v);
- }
- return;
- }
- WallpaperTileInfo info = (WallpaperTileInfo) v.getTag();
- if (info.isSelectable()) {
- if (mSelectedThumb != null) {
- mSelectedThumb.setSelected(false);
- mSelectedThumb = null;
- }
- mSelectedThumb = v;
- v.setSelected(true);
- // TODO: Remove this once the accessibility framework and
- // services have better support for selection state.
- v.announceForAccessibility(
- getString(R.string.announce_selection, v.getContentDescription()));
- }
- info.onClick(WallpaperPickerActivity.this);
- }
- };
- mLongClickListener = new View.OnLongClickListener() {
- // Called when the user long-clicks on someView
- public boolean onLongClick(View view) {
- CheckableFrameLayout c = (CheckableFrameLayout) view;
- c.toggle();
-
- if (mActionMode != null) {
- mActionMode.invalidate();
- } else {
- // Start the CAB using the ActionMode.Callback defined below
- mActionMode = startActionMode(mActionModeCallback);
- int childCount = mWallpapersView.getChildCount();
- for (int i = 0; i < childCount; i++) {
- mWallpapersView.getChildAt(i).setSelected(false);
- }
- }
- return true;
- }
- };
-
- // Populate the built-in wallpapers
- ArrayList<ResourceWallpaperInfo> wallpapers = findBundledWallpapers();
- mWallpapersView = (LinearLayout) findViewById(R.id.wallpaper_list);
- BuiltInWallpapersAdapter ia = new BuiltInWallpapersAdapter(this, wallpapers);
- populateWallpapersFromAdapter(mWallpapersView, ia, false, true);
-
- // Populate the saved wallpapers
- mSavedImages = new SavedWallpaperImages(this);
- mSavedImages.loadThumbnailsAndImageIdList();
- populateWallpapersFromAdapter(mWallpapersView, mSavedImages, true, true);
-
- // Populate the live wallpapers
- final LinearLayout liveWallpapersView =
- (LinearLayout) findViewById(R.id.live_wallpaper_list);
- final LiveWallpaperListAdapter a = new LiveWallpaperListAdapter(this);
- a.registerDataSetObserver(new DataSetObserver() {
- public void onChanged() {
- liveWallpapersView.removeAllViews();
- populateWallpapersFromAdapter(liveWallpapersView, a, false, false);
- initializeScrollForRtl();
- updateTileIndices();
- }
- });
-
- // Populate the third-party wallpaper pickers
- final LinearLayout thirdPartyWallpapersView =
- (LinearLayout) findViewById(R.id.third_party_wallpaper_list);
- final ThirdPartyWallpaperPickerListAdapter ta =
- new ThirdPartyWallpaperPickerListAdapter(this);
- populateWallpapersFromAdapter(thirdPartyWallpapersView, ta, false, false);
-
- // Add a tile for the Gallery
- LinearLayout masterWallpaperList = (LinearLayout) findViewById(R.id.master_wallpaper_list);
- FrameLayout pickImageTile = (FrameLayout) getLayoutInflater().
- inflate(R.layout.wallpaper_picker_image_picker_item, masterWallpaperList, false);
- setWallpaperItemPaddingToZero(pickImageTile);
- masterWallpaperList.addView(pickImageTile, 0);
-
- // Make its background the last photo taken on external storage
- Bitmap lastPhoto = getThumbnailOfLastPhoto();
- if (lastPhoto != null) {
- ImageView galleryThumbnailBg =
- (ImageView) pickImageTile.findViewById(R.id.wallpaper_image);
- galleryThumbnailBg.setImageBitmap(getThumbnailOfLastPhoto());
- int colorOverlay = getResources().getColor(R.color.wallpaper_picker_translucent_gray);
- galleryThumbnailBg.setColorFilter(colorOverlay, PorterDuff.Mode.SRC_ATOP);
-
- }
-
- PickImageInfo pickImageInfo = new PickImageInfo();
- pickImageTile.setTag(pickImageInfo);
- pickImageInfo.setView(pickImageTile);
- pickImageTile.setOnClickListener(mThumbnailOnClickListener);
- pickImageInfo.setView(pickImageTile);
-
- updateTileIndices();
-
- // Update the scroll for RTL
- initializeScrollForRtl();
-
- // Create smooth layout transitions for when items are deleted
- final LayoutTransition transitioner = new LayoutTransition();
- transitioner.setDuration(200);
- transitioner.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, 0);
- transitioner.setAnimator(LayoutTransition.DISAPPEARING, null);
- mWallpapersView.setLayoutTransition(transitioner);
-
- // Action bar
- // Show the custom action bar view
- final ActionBar actionBar = getActionBar();
- actionBar.setCustomView(R.layout.actionbar_set_wallpaper);
- actionBar.getCustomView().setOnClickListener(
- new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- if (mSelectedThumb != null) {
- WallpaperTileInfo info = (WallpaperTileInfo) mSelectedThumb.getTag();
- info.onSave(WallpaperPickerActivity.this);
- }
- }
- });
-
- // CAB for deleting items
- mActionModeCallback = new ActionMode.Callback() {
- // Called when the action mode is created; startActionMode() was called
- @Override
- public boolean onCreateActionMode(ActionMode mode, Menu menu) {
- // Inflate a menu resource providing context menu items
- MenuInflater inflater = mode.getMenuInflater();
- inflater.inflate(R.menu.cab_delete_wallpapers, menu);
- return true;
- }
-
- private int numCheckedItems() {
- int childCount = mWallpapersView.getChildCount();
- int numCheckedItems = 0;
- for (int i = 0; i < childCount; i++) {
- CheckableFrameLayout c = (CheckableFrameLayout) mWallpapersView.getChildAt(i);
- if (c.isChecked()) {
- numCheckedItems++;
- }
- }
- return numCheckedItems;
- }
-
- // Called each time the action mode is shown. Always called after onCreateActionMode,
- // but may be called multiple times if the mode is invalidated.
- @Override
- public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
- int numCheckedItems = numCheckedItems();
- if (numCheckedItems == 0) {
- mode.finish();
- return true;
- } else {
- mode.setTitle(getResources().getQuantityString(
- R.plurals.number_of_items_selected, numCheckedItems, numCheckedItems));
- return true;
- }
- }
-
- // Called when the user selects a contextual menu item
- @Override
- public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
- int itemId = item.getItemId();
- if (itemId == R.id.menu_delete) {
- int childCount = mWallpapersView.getChildCount();
- ArrayList<View> viewsToRemove = new ArrayList<View>();
- for (int i = 0; i < childCount; i++) {
- CheckableFrameLayout c =
- (CheckableFrameLayout) mWallpapersView.getChildAt(i);
- if (c.isChecked()) {
- WallpaperTileInfo info = (WallpaperTileInfo) c.getTag();
- info.onDelete(WallpaperPickerActivity.this);
- viewsToRemove.add(c);
- }
- }
- for (View v : viewsToRemove) {
- mWallpapersView.removeView(v);
- }
- updateTileIndices();
- mode.finish(); // Action picked, so close the CAB
- return true;
- } else {
- return false;
- }
- }
-
- // Called when the user exits the action mode
- @Override
- public void onDestroyActionMode(ActionMode mode) {
- int childCount = mWallpapersView.getChildCount();
- for (int i = 0; i < childCount; i++) {
- CheckableFrameLayout c = (CheckableFrameLayout) mWallpapersView.getChildAt(i);
- c.setChecked(false);
- }
- mSelectedThumb.setSelected(true);
- mActionMode = null;
- }
- };
- }
-
- private void initializeScrollForRtl() {
- final HorizontalScrollView scroll =
- (HorizontalScrollView) findViewById(R.id.wallpaper_scroll_container);
-
- if (scroll.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
- final ViewTreeObserver observer = scroll.getViewTreeObserver();
- observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
- public void onGlobalLayout() {
- LinearLayout masterWallpaperList =
- (LinearLayout) findViewById(R.id.master_wallpaper_list);
- scroll.scrollTo(masterWallpaperList.getWidth(), 0);
- scroll.getViewTreeObserver().removeOnGlobalLayoutListener(this);
- }
- });
- }
- }
-
- public boolean enableRotation() {
- return super.enableRotation() || Launcher.sForceEnableRotation;
- }
-
- protected Bitmap getThumbnailOfLastPhoto() {
- Cursor cursor = MediaStore.Images.Media.query(getContentResolver(),
- MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
- new String[] { MediaStore.Images.ImageColumns._ID,
- MediaStore.Images.ImageColumns.DATE_TAKEN},
- null, null, MediaStore.Images.ImageColumns.DATE_TAKEN + " DESC LIMIT 1");
- Bitmap thumb = null;
- if (cursor != null) {
- if (cursor.moveToFirst()) {
- int id = cursor.getInt(0);
- thumb = MediaStore.Images.Thumbnails.getThumbnail(getContentResolver(),
- id, MediaStore.Images.Thumbnails.MINI_KIND, null);
- }
- cursor.close();
- }
- return thumb;
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- menu.add(0, MENU_WALLPAPER_SCROLL, 0,
- R.string.wallpaper_scroll).setCheckable(true);
-
- return super.onCreateOptionsMenu(menu);
- }
-
- @Override
- public boolean onPrepareOptionsMenu(Menu menu) {
- MenuItem wallpaperScroll = menu.findItem(MENU_WALLPAPER_SCROLL);
-
- wallpaperScroll.setChecked(SettingsProvider.getBoolean(this,
- SettingsProvider.SETTINGS_UI_HOMESCREEN_SCROLLING_WALLPAPER_SCROLL,
- R.bool.preferences_interface_homescreen_scrolling_wallpaper_scroll_default));
-
- return super.onPrepareOptionsMenu(menu);
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- // Handle presses on the action bar items
- switch (item.getItemId()) {
- case MENU_WALLPAPER_SCROLL:
- SettingsProvider.get(this).edit()
- .putBoolean(SettingsProvider.SETTINGS_UI_HOMESCREEN_SCROLLING_WALLPAPER_SCROLL, !item.isChecked())
- .commit();
- return true;
- default:
- return super.onOptionsItemSelected(item);
- }
- }
-
- protected void onStop() {
- super.onStop();
- mWallpaperStrip = findViewById(R.id.wallpaper_strip);
- if (mWallpaperStrip.getAlpha() < 1f) {
- mWallpaperStrip.setAlpha(1f);
- mWallpaperStrip.setVisibility(View.VISIBLE);
- }
- mCropView.destroy();
- }
-
- protected void onSaveInstanceState(Bundle outState) {
- outState.putParcelableArrayList(TEMP_WALLPAPER_TILES, mTempWallpaperTiles);
- }
-
- protected void onRestoreInstanceState(Bundle savedInstanceState) {
- ArrayList<Uri> uris = savedInstanceState.getParcelableArrayList(TEMP_WALLPAPER_TILES);
- for (Uri uri : uris) {
- addTemporaryWallpaperTile(uri);
- }
- }
-
- private void populateWallpapersFromAdapter(ViewGroup parent, BaseAdapter adapter,
- boolean addLongPressHandler, boolean selectFirstTile) {
- for (int i = 0; i < adapter.getCount(); i++) {
- FrameLayout thumbnail = (FrameLayout) adapter.getView(i, null, parent);
- parent.addView(thumbnail, i);
- WallpaperTileInfo info = (WallpaperTileInfo) adapter.getItem(i);
- thumbnail.setTag(info);
- info.setView(thumbnail);
- if (addLongPressHandler) {
- addLongPressHandler(thumbnail);
- }
- thumbnail.setOnClickListener(mThumbnailOnClickListener);
- if (i == 0 && selectFirstTile) {
- mThumbnailOnClickListener.onClick(thumbnail);
- }
- }
- }
-
- private void updateTileIndices() {
- LinearLayout masterWallpaperList = (LinearLayout) findViewById(R.id.master_wallpaper_list);
- final int childCount = masterWallpaperList.getChildCount();
- final Resources res = getResources();
-
- // Do two passes; the first pass gets the total number of tiles
- int numTiles = 0;
- for (int passNum = 0; passNum < 2; passNum++) {
- int tileIndex = 0;
- for (int i = 0; i < childCount; i++) {
- View child = masterWallpaperList.getChildAt(i);
- LinearLayout subList;
-
- int subListStart;
- int subListEnd;
- if (child.getTag() instanceof WallpaperTileInfo) {
- subList = masterWallpaperList;
- subListStart = i;
- subListEnd = i + 1;
- } else { // if (child instanceof LinearLayout) {
- subList = (LinearLayout) child;
- subListStart = 0;
- subListEnd = subList.getChildCount();
- }
-
- for (int j = subListStart; j < subListEnd; j++) {
- WallpaperTileInfo info = (WallpaperTileInfo) subList.getChildAt(j).getTag();
- if (info.isNamelessWallpaper()) {
- if (passNum == 0) {
- numTiles++;
- } else {
- CharSequence label = res.getString(
- R.string.wallpaper_accessibility_name, ++tileIndex, numTiles);
- info.onIndexUpdated(label);
- }
- }
- }
- }
- }
- }
-
- private static Point getDefaultThumbnailSize(Resources res) {
- return new Point(res.getDimensionPixelSize(R.dimen.wallpaperThumbnailWidth),
- res.getDimensionPixelSize(R.dimen.wallpaperThumbnailHeight));
-
- }
-
- private static Bitmap createThumbnail(Point size, Context context, Uri uri, byte[] imageBytes,
- Resources res, int resId, int rotation, boolean leftAligned) {
- int width = size.x;
- int height = size.y;
-
- BitmapCropTask cropTask;
- if (uri != null) {
- cropTask = new BitmapCropTask(
- context, uri, null, rotation, width, height, false, true, null);
- } else if (imageBytes != null) {
- cropTask = new BitmapCropTask(
- imageBytes, null, rotation, width, height, false, true, null);
- } else {
- cropTask = new BitmapCropTask(
- context, res, resId, null, rotation, width, height, false, true, null);
- }
- Point bounds = cropTask.getImageBounds();
- if (bounds == null || bounds.x == 0 || bounds.y == 0) {
- return null;
- }
-
- Matrix rotateMatrix = new Matrix();
- rotateMatrix.setRotate(rotation);
- float[] rotatedBounds = new float[] { bounds.x, bounds.y };
- rotateMatrix.mapPoints(rotatedBounds);
- rotatedBounds[0] = Math.abs(rotatedBounds[0]);
- rotatedBounds[1] = Math.abs(rotatedBounds[1]);
-
- RectF cropRect = WallpaperCropActivity.getMaxCropRect(
- (int) rotatedBounds[0], (int) rotatedBounds[1], width, height, leftAligned);
- cropTask.setCropBounds(cropRect);
-
- if (cropTask.cropBitmap()) {
- return cropTask.getCroppedBitmap();
- } else {
- return null;
- }
- }
-
- private void addTemporaryWallpaperTile(Uri uri) {
- mTempWallpaperTiles.add(uri);
- // Add a tile for the image picked from Gallery
- FrameLayout pickedImageThumbnail = (FrameLayout) getLayoutInflater().
- inflate(R.layout.wallpaper_picker_item, mWallpapersView, false);
- setWallpaperItemPaddingToZero(pickedImageThumbnail);
-
- // Load the thumbnail
- ImageView image = (ImageView) pickedImageThumbnail.findViewById(R.id.wallpaper_image);
- Point defaultSize = getDefaultThumbnailSize(this.getResources());
- int rotation = WallpaperCropActivity.getRotationFromExif(this, uri);
- Bitmap thumb = createThumbnail(defaultSize, this, uri, null, null, 0, rotation, false);
- if (thumb != null) {
- image.setImageBitmap(thumb);
- Drawable thumbDrawable = image.getDrawable();
- thumbDrawable.setDither(true);
- } else {
- Log.e(TAG, "Error loading thumbnail for uri=" + uri);
- }
- mWallpapersView.addView(pickedImageThumbnail, 0);
-
- UriWallpaperInfo info = new UriWallpaperInfo(uri);
- pickedImageThumbnail.setTag(info);
- info.setView(pickedImageThumbnail);
- addLongPressHandler(pickedImageThumbnail);
- updateTileIndices();
- pickedImageThumbnail.setOnClickListener(mThumbnailOnClickListener);
- mThumbnailOnClickListener.onClick(pickedImageThumbnail);
- }
-
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (requestCode == IMAGE_PICK && resultCode == RESULT_OK) {
- if (data != null && data.getData() != null) {
- Uri uri = data.getData();
- addTemporaryWallpaperTile(uri);
- }
- } else if (requestCode == PICK_WALLPAPER_THIRD_PARTY_ACTIVITY) {
- setResult(RESULT_OK);
- finish();
- } else if (requestCode == PICK_LIVE_WALLPAPER) {
- WallpaperManager wm = WallpaperManager.getInstance(this);
- final WallpaperInfo oldLiveWallpaper = mLiveWallpaperInfoOnPickerLaunch;
- WallpaperInfo newLiveWallpaper = wm.getWallpaperInfo();
- // Try to figure out if a live wallpaper was set;
- if (newLiveWallpaper != null &&
- (oldLiveWallpaper == null ||
- !oldLiveWallpaper.getComponent().equals(newLiveWallpaper.getComponent()))) {
- // Return if a live wallpaper was set
- setResult(RESULT_OK);
- finish();
- }
- }
- }
-
- static void setWallpaperItemPaddingToZero(FrameLayout frameLayout) {
- frameLayout.setPadding(0, 0, 0, 0);
- frameLayout.setForeground(new ZeroPaddingDrawable(frameLayout.getForeground()));
- }
-
- private void addLongPressHandler(View v) {
- v.setOnLongClickListener(mLongClickListener);
- }
-
- private ArrayList<ResourceWallpaperInfo> findBundledWallpapers() {
- ArrayList<ResourceWallpaperInfo> bundledWallpapers =
- new ArrayList<ResourceWallpaperInfo>(24);
-
- Pair<ApplicationInfo, Integer> r = getWallpaperArrayResourceId();
- if (r != null) {
- try {
- Resources wallpaperRes = getPackageManager().getResourcesForApplication(r.first);
- bundledWallpapers = addWallpapers(wallpaperRes, r.first.packageName, r.second);
- } catch (PackageManager.NameNotFoundException e) {
- }
- }
-
- // Add an entry for the default wallpaper (stored in system resources)
- ResourceWallpaperInfo defaultWallpaperInfo = getDefaultWallpaperInfo();
- if (defaultWallpaperInfo != null) {
- bundledWallpapers.add(0, defaultWallpaperInfo);
- }
- return bundledWallpapers;
- }
-
- private ResourceWallpaperInfo getDefaultWallpaperInfo() {
- Resources sysRes = Resources.getSystem();
- int resId = sysRes.getIdentifier("default_wallpaper", "drawable", "android");
-
- File defaultThumbFile = new File(getFilesDir(), "default_thumb.jpg");
- Bitmap thumb = null;
- boolean defaultWallpaperExists = false;
- if (defaultThumbFile.exists()) {
- thumb = BitmapFactory.decodeFile(defaultThumbFile.getAbsolutePath());
- defaultWallpaperExists = true;
- } else {
- Resources res = getResources();
- Point defaultThumbSize = getDefaultThumbnailSize(res);
- int rotation = WallpaperCropActivity.getRotationFromExif(res, resId);
- thumb = createThumbnail(
- defaultThumbSize, this, null, null, sysRes, resId, rotation, false);
- if (thumb != null) {
- try {
- defaultThumbFile.createNewFile();
- FileOutputStream thumbFileStream =
- openFileOutput(defaultThumbFile.getName(), Context.MODE_PRIVATE);
- thumb.compress(Bitmap.CompressFormat.JPEG, 95, thumbFileStream);
- thumbFileStream.close();
- defaultWallpaperExists = true;
- } catch (IOException e) {
- Log.e(TAG, "Error while writing default wallpaper thumbnail to file " + e);
- defaultThumbFile.delete();
- }
- }
- }
- if (defaultWallpaperExists) {
- return new ResourceWallpaperInfo(sysRes, resId, new BitmapDrawable(thumb));
- }
- return null;
- }
-
- public Pair<ApplicationInfo, Integer> getWallpaperArrayResourceId() {
- // Context.getPackageName() may return the "original" package name,
- // com.android.launcher3; Resources needs the real package name,
- // com.android.launcher3. So we ask Resources for what it thinks the
- // package name should be.
- final String packageName = getResources().getResourcePackageName(R.array.wallpapers);
- try {
- ApplicationInfo info = getPackageManager().getApplicationInfo(packageName, 0);
- return new Pair<ApplicationInfo, Integer>(info, R.array.wallpapers);
- } catch (PackageManager.NameNotFoundException e) {
- return null;
- }
- }
-
- private ArrayList<ResourceWallpaperInfo> addWallpapers(
- Resources res, String packageName, int listResId) {
- ArrayList<ResourceWallpaperInfo> bundledWallpapers =
- new ArrayList<ResourceWallpaperInfo>(24);
- final String[] extras = res.getStringArray(listResId);
- for (String extra : extras) {
- int resId = res.getIdentifier(extra, "drawable", packageName);
- if (resId != 0) {
- final int thumbRes = res.getIdentifier(extra + "_small", "drawable", packageName);
-
- if (thumbRes != 0) {
- ResourceWallpaperInfo wallpaperInfo =
- new ResourceWallpaperInfo(res, resId, res.getDrawable(thumbRes));
- bundledWallpapers.add(wallpaperInfo);
- // Log.d(TAG, "add: [" + packageName + "]: " + extra + " (" + res + ")");
- }
- } else {
- Log.e(TAG, "Couldn't find wallpaper " + extra);
- }
- }
- return bundledWallpapers;
- }
-
- public CropView getCropView() {
- return mCropView;
- }
-
- public SavedWallpaperImages getSavedImages() {
- return mSavedImages;
- }
-
- public void onLiveWallpaperPickerLaunch() {
- mLiveWallpaperInfoOnPickerLaunch = WallpaperManager.getInstance(this).getWallpaperInfo();
- }
-
- static class ZeroPaddingDrawable extends LevelListDrawable {
- public ZeroPaddingDrawable(Drawable d) {
- super();
- addLevel(0, 0, d);
- setLevel(0);
- }
-
- @Override
- public boolean getPadding(Rect padding) {
- padding.set(0, 0, 0, 0);
- return true;
- }
- }
-
- private static class BuiltInWallpapersAdapter extends BaseAdapter implements ListAdapter {
- private LayoutInflater mLayoutInflater;
- private ArrayList<ResourceWallpaperInfo> mWallpapers;
-
- BuiltInWallpapersAdapter(Activity activity, ArrayList<ResourceWallpaperInfo> wallpapers) {
- mLayoutInflater = activity.getLayoutInflater();
- mWallpapers = wallpapers;
- }
-
- public int getCount() {
- return mWallpapers.size();
- }
-
- public ResourceWallpaperInfo getItem(int position) {
- return mWallpapers.get(position);
- }
-
- public long getItemId(int position) {
- return position;
- }
-
- public View getView(int position, View convertView, ViewGroup parent) {
- Drawable thumb = mWallpapers.get(position).mThumb;
- if (thumb == null) {
- Log.e(TAG, "Error decoding thumbnail for wallpaper #" + position);
- }
- return createImageTileView(mLayoutInflater, position, convertView, parent, thumb);
- }
- }
-
- public static View createImageTileView(LayoutInflater layoutInflater, int position,
- View convertView, ViewGroup parent, Drawable thumb) {
- View view;
-
- if (convertView == null) {
- view = layoutInflater.inflate(R.layout.wallpaper_picker_item, parent, false);
- } else {
- view = convertView;
- }
-
- setWallpaperItemPaddingToZero((FrameLayout) view);
-
- ImageView image = (ImageView) view.findViewById(R.id.wallpaper_image);
-
- if (thumb != null) {
- image.setImageDrawable(thumb);
- thumb.setDither(true);
- }
-
- return view;
- }
-}
diff --git a/src/com/android/launcher3/WallpaperRootView.java b/src/com/android/launcher3/WallpaperRootView.java
deleted file mode 100644
index 7b0d4f147..000000000
--- a/src/com/android/launcher3/WallpaperRootView.java
+++ /dev/null
@@ -1,39 +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.Context;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.widget.RelativeLayout;
-
-public class WallpaperRootView extends RelativeLayout {
- private final WallpaperCropActivity a;
- public WallpaperRootView(Context context, AttributeSet attrs) {
- super(context, attrs);
- a = (WallpaperCropActivity) context;
- }
- public WallpaperRootView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- a = (WallpaperCropActivity) context;
- }
-
- protected boolean fitSystemWindows(Rect insets) {
- a.setWallpaperStripYOffset(insets.bottom);
- return true;
- }
-}
diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java
index 07b4f6f0a..3db0b51ad 100644
--- a/src/com/android/launcher3/WidgetPreviewLoader.java
+++ b/src/com/android/launcher3/WidgetPreviewLoader.java
@@ -4,11 +4,13 @@ import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.ContentValues;
import android.content.Context;
+import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteDiskIOException;
import android.database.sqlite.SQLiteOpenHelper;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
@@ -100,6 +102,7 @@ class BitmapFactoryOptionsCache extends SoftReferenceThreadLocal<BitmapFactory.O
public class WidgetPreviewLoader {
static final String TAG = "WidgetPreviewLoader";
+ static final String ANDROID_INCREMENTAL_VERSION_NAME_KEY = "android.incremental.version";
private int mPreviewBitmapWidth;
private int mPreviewBitmapHeight;
@@ -147,6 +150,26 @@ public class WidgetPreviewLoader {
mDb = app.getWidgetPreviewCacheDb();
mLoadedPreviews = new HashMap<String, WeakReference<Bitmap>>();
mUnusedBitmaps = new ArrayList<SoftReference<Bitmap>>();
+
+ SharedPreferences sp = context.getSharedPreferences(
+ LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE);
+ final String lastVersionName = sp.getString(ANDROID_INCREMENTAL_VERSION_NAME_KEY, null);
+ final String versionName = android.os.Build.VERSION.INCREMENTAL;
+ if (!versionName.equals(lastVersionName)) {
+ // clear all the previews whenever the system version changes, to ensure that previews
+ // are up-to-date for any apps that might have been updated with the system
+ clearDb();
+
+ SharedPreferences.Editor editor = sp.edit();
+ editor.putString(ANDROID_INCREMENTAL_VERSION_NAME_KEY, versionName);
+ editor.commit();
+ }
+ }
+
+ public void recreateDb() {
+ LauncherAppState app = LauncherAppState.getInstance();
+ app.recreateWidgetPreviewDb();
+ mDb = app.getWidgetPreviewCacheDb();
}
public void setPreviewSize(int previewWidth, int previewHeight,
@@ -331,7 +354,20 @@ public class WidgetPreviewLoader {
preview.compress(Bitmap.CompressFormat.PNG, 100, stream);
values.put(CacheDb.COLUMN_PREVIEW_BITMAP, stream.toByteArray());
values.put(CacheDb.COLUMN_SIZE, mSize);
- db.insert(CacheDb.TABLE_NAME, null, values);
+ try {
+ db.insert(CacheDb.TABLE_NAME, null, values);
+ } catch (SQLiteDiskIOException e) {
+ recreateDb();
+ }
+ }
+
+ private void clearDb() {
+ SQLiteDatabase db = mDb.getWritableDatabase();
+ // Delete everything
+ try {
+ db.delete(CacheDb.TABLE_NAME, null, null);
+ } catch (SQLiteDiskIOException e) {
+ }
}
public static void removePackageFromDb(final CacheDb cacheDb, final String packageName) {
@@ -341,13 +377,17 @@ public class WidgetPreviewLoader {
new AsyncTask<Void, Void, Void>() {
public Void doInBackground(Void ... args) {
SQLiteDatabase db = cacheDb.getWritableDatabase();
- db.delete(CacheDb.TABLE_NAME,
- CacheDb.COLUMN_NAME + " LIKE ? OR " +
- CacheDb.COLUMN_NAME + " LIKE ?", // SELECT query
- new String[] {
- WIDGET_PREFIX + packageName + "/%",
- SHORTCUT_PREFIX + packageName + "/%"} // args to SELECT query
- );
+ try {
+ db.delete(CacheDb.TABLE_NAME,
+ CacheDb.COLUMN_NAME + " LIKE ? OR " +
+ CacheDb.COLUMN_NAME + " LIKE ?", // SELECT query
+ new String[] {
+ WIDGET_PREFIX + packageName + "/%",
+ SHORTCUT_PREFIX + packageName + "/%"
+ } // args to SELECT query
+ );
+ } catch (SQLiteDiskIOException e) {
+ }
synchronized(sInvalidPackages) {
sInvalidPackages.remove(packageName);
}
@@ -360,9 +400,12 @@ public class WidgetPreviewLoader {
new AsyncTask<Void, Void, Void>() {
public Void doInBackground(Void ... args) {
SQLiteDatabase db = cacheDb.getWritableDatabase();
- db.delete(CacheDb.TABLE_NAME,
- CacheDb.COLUMN_NAME + " = ? ", // SELECT query
- new String[] { objectName }); // args to SELECT query
+ try {
+ db.delete(CacheDb.TABLE_NAME,
+ CacheDb.COLUMN_NAME + " = ? ", // SELECT query
+ new String[] { objectName }); // args to SELECT query
+ } catch (SQLiteDiskIOException e) {
+ }
return null;
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
@@ -374,14 +417,20 @@ public class WidgetPreviewLoader {
CacheDb.COLUMN_SIZE + " = ?";
}
SQLiteDatabase db = mDb.getReadableDatabase();
- Cursor result = db.query(CacheDb.TABLE_NAME,
- new String[] { CacheDb.COLUMN_PREVIEW_BITMAP }, // cols to return
- mCachedSelectQuery, // select query
- new String[] { name, mSize }, // args to select query
- null,
- null,
- null,
- null);
+ Cursor result;
+ try {
+ result = db.query(CacheDb.TABLE_NAME,
+ new String[] { CacheDb.COLUMN_PREVIEW_BITMAP }, // cols to return
+ mCachedSelectQuery, // select query
+ new String[] { name, mSize }, // args to select query
+ null,
+ null,
+ null,
+ null);
+ } catch (SQLiteDiskIOException e) {
+ recreateDb();
+ return null;
+ }
if (result.getCount() > 0) {
result.moveToFirst();
byte[] blob = result.getBlob(0);
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 37bd8aa73..7e3584115 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -22,6 +22,7 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.LayoutTransition;
import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
@@ -30,7 +31,6 @@ import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -43,6 +43,7 @@ import android.graphics.Rect;
import android.graphics.Region.Op;
import android.graphics.drawable.Drawable;
import android.net.Uri;
+import android.os.AsyncTask;
import android.os.IBinder;
import android.os.Parcelable;
import android.support.v4.view.ViewCompat;
@@ -55,6 +56,8 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityManager;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.widget.TextView;
@@ -62,6 +65,7 @@ import android.widget.TextView;
import com.android.launcher3.FolderIcon.FolderRingAnimator;
import com.android.launcher3.Launcher.CustomContentCallbacks;
import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.backup.BackupProtos;
import com.android.launcher3.settings.SettingsProvider;
import java.util.ArrayList;
@@ -87,6 +91,9 @@ public class Workspace extends SmoothPagedView
private static final int CHILDREN_OUTLINE_FADE_OUT_DURATION = 375;
private static final int CHILDREN_OUTLINE_FADE_IN_DURATION = 100;
+ protected static final int SNAP_OFF_EMPTY_SCREEN_DURATION = 400;
+ protected static final int FADE_EMPTY_SCREEN_DURATION = 150;
+
private static final int BACKGROUND_FADE_OUT_DURATION = 350;
private static final int ADJACENT_SCREEN_DROP_DURATION = 300;
private static final int FLING_THRESHOLD_VELOCITY = 500;
@@ -119,14 +126,15 @@ public class Workspace extends SmoothPagedView
private ShortcutAndWidgetContainer mDragSourceInternal;
private static boolean sAccessibilityEnabled;
- // The screen ids used for the empty screens always present to the left/right.
- private final static long EXTRA_EMPTY_SCREEN_LEFT_ID = -201;
- private final static long EXTRA_EMPTY_SCREEN_RIGHT_ID = -202;
+ // The screen id used for the empty screen always present to the right.
+ private final static long EXTRA_EMPTY_SCREEN_ID = -201;
private final static long CUSTOM_CONTENT_SCREEN_ID = -301;
private HashMap<Long, CellLayout> mWorkspaceScreens = new HashMap<Long, CellLayout>();
private ArrayList<Long> mScreenOrder = new ArrayList<Long>();
+ private Runnable mRemoveEmptyScreenRunnable;
+
/**
* CellInfo for the cell that is currently being dragged
*/
@@ -177,7 +185,6 @@ public class Workspace extends SmoothPagedView
private SpringLoadedDragController mSpringLoadedDragController;
private float mSpringLoadedShrinkFactor;
private float mOverviewModeShrinkFactor;
- private int mOverviewModePageOffset;
// State variable that indicates whether the pages are small (ie when you're
// in all apps or customize mode)
@@ -206,7 +213,12 @@ public class Workspace extends SmoothPagedView
private boolean mWorkspaceFadeInAdjacentScreens;
WallpaperOffsetInterpolator mWallpaperOffset;
+
private boolean mScrollWallpaper;
+ private boolean mWallpaperIsLiveWallpaper;
+ private int mNumPagesForWallpaperParallax;
+ private float mLastSetWallpaperOffsetSteps = 0;
+
private Runnable mDelayedResizeRunnable;
private Point mDisplaySize = new Point();
@@ -214,7 +226,7 @@ public class Workspace extends SmoothPagedView
// Variables relating to the creation of user folders by hovering shortcuts over shortcuts
private static final int FOLDER_CREATION_TIMEOUT = 0;
- private static final int REORDER_TIMEOUT = 250;
+ public static final int REORDER_TIMEOUT = 350;
private final Alarm mFolderCreationAlarm = new Alarm();
private final Alarm mReorderAlarm = new Alarm();
private FolderRingAnimator mDragFolderRingAnimator = null;
@@ -325,16 +337,17 @@ public class Workspace extends SmoothPagedView
mLauncher = (Launcher) context;
final Resources res = getResources();
+
mFadeInAdjacentScreens = false;
mWallpaperManager = WallpaperManager.getInstance(context);
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.Workspace, defStyle, 0);
mSpringLoadedShrinkFactor =
res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f;
- mOverviewModeShrinkFactor =
- res.getInteger(R.integer.config_workspaceOverviewShrinkPercentage) / 100.0f;
- mOverviewModePageOffset = res.getDimensionPixelSize(R.dimen.overview_mode_page_offset);
+ mOverviewModeShrinkFactor = grid.getOverviewModeScale();
mCameraDistance = res.getInteger(R.integer.config_cameraDistance);
mDefaultPage = a.getInt(R.styleable.Workspace_defaultScreen, 1);
mDefaultScreenId = SettingsProvider.getLongCustomDefault(context,
@@ -354,6 +367,14 @@ public class Workspace extends SmoothPagedView
@Override
public void setInsets(Rect insets) {
mInsets.set(insets);
+
+ CellLayout customScreen = getScreenWithId(CUSTOM_CONTENT_SCREEN_ID);
+ if (customScreen != null) {
+ View customContent = customScreen.getShortcutsAndWidgets().getChildAt(0);
+ if (customContent instanceof Insettable) {
+ ((Insettable) customContent).setInsets(mInsets);
+ }
+ }
}
// estimate the size of a widget with spans hSpan, vSpan. return MAX_VALUE for each
@@ -399,7 +420,7 @@ public class Workspace extends SmoothPagedView
@Override
public void run() {
if (mIsDragOccuring) {
- addExtraEmptyScreensOnDrag();
+ addExtraEmptyScreenOnDrag();
}
}
});
@@ -414,11 +435,6 @@ public class Workspace extends SmoothPagedView
InstallShortcutReceiver.disableAndFlushInstallQueue(getContext());
UninstallShortcutReceiver.disableAndFlushUninstallQueue(getContext());
- // Disable all layout transitions before removing left extra pages to ensure that we don't get
- // the transition animations competing with us changing the scroll when we remove it
- disableLayoutTransitions();
- removeExtraEmptyScreens();
- enableLayoutTransitions();
mDragSourceInternal = null;
mLauncher.onInteractionEnd();
}
@@ -438,9 +454,7 @@ public class Workspace extends SmoothPagedView
setClipToPadding(false);
setChildrenDrawnWithCacheEnabled(true);
- // This is a bit of a hack to account for the fact that we translate the workspace
- // up a bit, and still need to draw the background covering the whole screen.
- setMinScale(mOverviewModeShrinkFactor - 0.2f);
+ setMinScale(mOverviewModeShrinkFactor);
setupLayoutTransition();
final Resources res = getResources();
@@ -456,6 +470,9 @@ public class Workspace extends SmoothPagedView
mMaxDistanceForFolderCreation = (0.55f * grid.iconSizePx);
mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * mDensity);
+
+ // Set the wallpaper dimensions when Launcher starts up
+ setWallpaperDimension();
}
private void setupLayoutTransition() {
@@ -550,7 +567,7 @@ public class Workspace extends SmoothPagedView
public long insertNewWorkspaceScreenBeforeEmptyScreen(long screenId) {
// Find the index to insert this view into. If the empty screen exists, then
// insert it before that.
- int insertIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_RIGHT_ID);
+ int insertIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
if (insertIndex < 0) {
insertIndex = mScreenOrder.size();
}
@@ -562,6 +579,10 @@ public class Workspace extends SmoothPagedView
}
public long insertNewWorkspaceScreen(long screenId, int insertIndex) {
+ // Log to disk
+ Launcher.addDumpLog(TAG, "11683562 - insertNewWorkspaceScreen(): " + screenId +
+ " at index: " + insertIndex, true);
+
if (mWorkspaceScreens.containsKey(screenId)) {
throw new RuntimeException("Screen id " + screenId + " already exists!");
}
@@ -585,9 +606,10 @@ public class Workspace extends SmoothPagedView
return screenId;
}
- public void createCustomContentPage() {
+ public void createCustomContentContainer() {
CellLayout customScreen = (CellLayout)
mLauncher.getLayoutInflater().inflate(R.layout.workspace_screen, null);
+ customScreen.disableBackground();
mWorkspaceScreens.put(CUSTOM_CONTENT_SCREEN_ID, customScreen);
mScreenOrder.add(0, CUSTOM_CONTENT_SCREEN_ID);
@@ -598,7 +620,7 @@ public class Workspace extends SmoothPagedView
addFullScreenPage(customScreen);
// Update the custom content hint
- mLauncher.updateCustomContentHintVisibility();
+ mLauncher.getLauncherClings().updateCustomContentHintVisibility();
if (mRestorePage != INVALID_RESTORE_PAGE) {
mRestorePage = mRestorePage + 1;
} else {
@@ -624,7 +646,7 @@ public class Workspace extends SmoothPagedView
mCustomContentCallbacks = null;
// Update the custom content hint
- mLauncher.updateCustomContentHintVisibility();
+ mLauncher.getLauncherClings().updateCustomContentHintVisibility();
if (mRestorePage != INVALID_RESTORE_PAGE) {
mRestorePage = mRestorePage - 1;
} else {
@@ -648,6 +670,12 @@ public class Workspace extends SmoothPagedView
if (customContent instanceof Insettable) {
((Insettable)customContent).setInsets(mInsets);
}
+
+ // Verify that the child is removed from any existing parent.
+ if (customContent.getParent() instanceof ViewGroup) {
+ ViewGroup parent = (ViewGroup) customContent.getParent();
+ parent.removeView(customContent);
+ }
customScreen.removeAllViews();
customScreen.addViewToCellLayout(customContent, 0, 0, lp, true);
mCustomContentDescription = description;
@@ -655,89 +683,172 @@ public class Workspace extends SmoothPagedView
mCustomContentCallbacks = callbacks;
}
- public void addExtraEmptyScreensOnDrag() {
- boolean addLeftScreen = true;
- boolean addRightScreen = true;
+ public void addExtraEmptyScreenOnDrag() {
+ // Log to disk
+ Launcher.addDumpLog(TAG, "11683562 - addExtraEmptyScreenOnDrag()", true);
+
+ boolean lastChildOnScreen = false;
+ boolean childOnFinalScreen = false;
+
+ // Cancel any pending removal of empty screen
+ mRemoveEmptyScreenRunnable = null;
if (mDragSourceInternal != null) {
if (mDragSourceInternal.getChildCount() == 1) {
- CellLayout cl = (CellLayout) mDragSourceInternal.getParent();
- addLeftScreen = indexOfChild(cl) != 0;
- addRightScreen = indexOfChild(cl) != getChildCount() - 1;
+ lastChildOnScreen = true;
+ }
+ CellLayout cl = (CellLayout) mDragSourceInternal.getParent();
+ if (indexOfChild(cl) == getChildCount() - 1) {
+ childOnFinalScreen = true;
}
}
- if (addLeftScreen && !mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_LEFT_ID)) {
- insertNewWorkspaceScreen(EXTRA_EMPTY_SCREEN_LEFT_ID, 0);
- setCurrentPage(getCurrentPage() + 1);
+ // If this is the last item on the final screen
+ if (lastChildOnScreen && childOnFinalScreen) {
+ return;
}
- if (addRightScreen && !mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_RIGHT_ID)) {
- insertNewWorkspaceScreen(EXTRA_EMPTY_SCREEN_RIGHT_ID);
+ if (!mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID)) {
+ insertNewWorkspaceScreen(EXTRA_EMPTY_SCREEN_ID);
}
}
public boolean addExtraEmptyScreen() {
- if (!mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_RIGHT_ID)) {
- insertNewWorkspaceScreen(EXTRA_EMPTY_SCREEN_RIGHT_ID);
+ // Log to disk
+ Launcher.addDumpLog(TAG, "11683562 - addExtraEmptyScreen()", true);
+
+ if (!mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID)) {
+ insertNewWorkspaceScreen(EXTRA_EMPTY_SCREEN_ID);
return true;
}
return false;
}
- public void removeExtraEmptyScreenLeft() {
- if (hasExtraEmptyScreenLeft()) {
- CellLayout cl = mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_LEFT_ID);
- mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_LEFT_ID);
- mScreenOrder.remove(EXTRA_EMPTY_SCREEN_LEFT_ID);
- setCurrentPage(mCurrentPage - 1);
- removeView(cl);
- }
- }
+ private void convertFinalScreenToEmptyScreenIfNecessary() {
+ // Log to disk
+ Launcher.addDumpLog(TAG, "11683562 - convertFinalScreenToEmptyScreenIfNecessary()", true);
- public void removeExtraEmptyScreenRight() {
- if (hasExtraEmptyScreenRight()) {
- CellLayout cl = mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_RIGHT_ID);
- mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_RIGHT_ID);
- mScreenOrder.remove(EXTRA_EMPTY_SCREEN_RIGHT_ID);
- removeView(cl);
+ if (hasExtraEmptyScreen() || mScreenOrder.size() == 0) return;
+ long finalScreenId = mScreenOrder.get(mScreenOrder.size() - 1);
+
+ if (finalScreenId == CUSTOM_CONTENT_SCREEN_ID) return;
+ CellLayout finalScreen = mWorkspaceScreens.get(finalScreenId);
+
+ // If the final screen is empty, convert it to the extra empty screen
+ if (finalScreen.getShortcutsAndWidgets().getChildCount() == 0 &&
+ !finalScreen.isDropPending()) {
+ mWorkspaceScreens.remove(finalScreenId);
+ mScreenOrder.remove(finalScreenId);
+
+ // if this is the last non-custom content screen, convert it to the empty screen
+ mWorkspaceScreens.put(EXTRA_EMPTY_SCREEN_ID, finalScreen);
+ mScreenOrder.add(EXTRA_EMPTY_SCREEN_ID);
+
+ // Update the model if we have changed any screens
+ mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
+ Launcher.addDumpLog(TAG, "11683562 - extra empty screen: " + finalScreenId, true);
}
}
- public void removeExtraEmptyScreens() {
- removeExtraEmptyScreenLeft();
- removeExtraEmptyScreenRight();
+ public void removeExtraEmptyScreen(final boolean animate, final Runnable onComplete) {
+ removeExtraEmptyScreen(animate, onComplete, 0, false);
}
- public boolean hasExtraEmptyScreenLeft() {
- return mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_LEFT_ID) && getChildCount() - numCustomPages() > 1;
- }
+ public void removeExtraEmptyScreen(final boolean animate, final Runnable onComplete,
+ final int delay, final boolean stripEmptyScreens) {
+ // Log to disk
+ Launcher.addDumpLog(TAG, "11683562 - removeExtraEmptyScreen()", true);
+ if (delay > 0) {
+ postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ removeExtraEmptyScreen(animate, onComplete, 0, stripEmptyScreens);
+ }
+
+ }, delay);
+ return;
+ }
- public boolean hasExtraEmptyScreenRight() {
- return mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_RIGHT_ID) && getChildCount() - numCustomPages() > 1;
+ convertFinalScreenToEmptyScreenIfNecessary();
+ if (hasExtraEmptyScreen()) {
+ int emptyIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
+ if (getNextPage() == emptyIndex) {
+ snapToPage(getNextPage() - 1, SNAP_OFF_EMPTY_SCREEN_DURATION);
+ fadeAndRemoveEmptyScreen(SNAP_OFF_EMPTY_SCREEN_DURATION, FADE_EMPTY_SCREEN_DURATION,
+ onComplete, stripEmptyScreens);
+ } else {
+ fadeAndRemoveEmptyScreen(0, FADE_EMPTY_SCREEN_DURATION,
+ onComplete, stripEmptyScreens);
+ }
+ return;
+ } else if (stripEmptyScreens) {
+ // If we're not going to strip the empty screens after removing
+ // the extra empty screen, do it right away.
+ stripEmptyScreens();
+ }
+
+ if (onComplete != null) {
+ onComplete.run();
+ }
}
- public boolean hasExtraEmptyScreens() {
- return hasExtraEmptyScreenLeft() || hasExtraEmptyScreenRight();
+ private void fadeAndRemoveEmptyScreen(int delay, int duration, final Runnable onComplete,
+ final boolean stripEmptyScreens) {
+ // Log to disk
+ // XXX: Do we need to update LM workspace screens below?
+ Launcher.addDumpLog(TAG, "11683562 - fadeAndRemoveEmptyScreen()", true);
+ PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0f);
+ PropertyValuesHolder bgAlpha = PropertyValuesHolder.ofFloat("backgroundAlpha", 0f);
+
+ final CellLayout cl = mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID);
+
+ mRemoveEmptyScreenRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (hasExtraEmptyScreen()) {
+ mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID);
+ mScreenOrder.remove(EXTRA_EMPTY_SCREEN_ID);
+ removeView(cl);
+ if (stripEmptyScreens) {
+ stripEmptyScreens();
+ }
+ }
+ }
+ };
+
+ ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(cl, alpha, bgAlpha);
+ oa.setDuration(duration);
+ oa.setStartDelay(delay);
+ oa.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (mRemoveEmptyScreenRunnable != null) {
+ mRemoveEmptyScreenRunnable.run();
+ }
+ if (onComplete != null) {
+ onComplete.run();
+ }
+ }
+ });
+ oa.start();
}
- public int numExtraEmptyScreens() {
- return (hasExtraEmptyScreenLeft() ? 1 : 0) + (hasExtraEmptyScreenRight() ? 1 : 0);
+ public boolean hasExtraEmptyScreen() {
+ int nScreens = getChildCount();
+ nScreens = nScreens - numCustomPages();
+ return mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID) && nScreens > 1;
}
- public long commitExtraEmptyScreen(long screenId) {
- int index = getPageIndexForScreenId(screenId);
- CellLayout cl = mWorkspaceScreens.get(screenId);
- mWorkspaceScreens.remove(screenId);
- mScreenOrder.remove(screenId);
+ public long commitExtraEmptyScreen() {
+ // Log to disk
+ Launcher.addDumpLog(TAG, "11683562 - commitExtraEmptyScreen()", true);
+ int index = getPageIndexForScreenId(EXTRA_EMPTY_SCREEN_ID);
+ CellLayout cl = mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID);
+ mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID);
+ mScreenOrder.remove(EXTRA_EMPTY_SCREEN_ID);
long newId = LauncherAppState.getLauncherProvider().generateNewScreenId();
mWorkspaceScreens.put(newId, cl);
-
- if (screenId == EXTRA_EMPTY_SCREEN_LEFT_ID) {
- mScreenOrder.add(numCustomPages(), newId);
- } else {
- mScreenOrder.add(newId);
- }
+ mScreenOrder.add(newId);
// Update the page indicator marker
if (getPageIndicator() != null) {
@@ -782,6 +893,14 @@ public class Workspace extends SmoothPagedView
}
public void stripEmptyScreens() {
+ // Log to disk
+ Launcher.addDumpLog(TAG, "11683562 - stripEmptyScreens()", true);
+
+ if (mLauncher.isWorkspaceLoading()) {
+ // Don't strip empty screens if the workspace is still loading
+ Launcher.addDumpLog(TAG, " - workspace loading, skip", true);
+ return;
+ }
if (isPageMoving()) {
mStripScreensOnPageStopMoving = true;
return;
@@ -802,6 +921,7 @@ public class Workspace extends SmoothPagedView
int pageShift = 0;
for (Long id: removeScreens) {
+ Launcher.addDumpLog(TAG, "11683562 - removing id: " + id, true);
CellLayout cl = mWorkspaceScreens.get(id);
mWorkspaceScreens.remove(id);
mScreenOrder.remove(id);
@@ -813,8 +933,9 @@ public class Workspace extends SmoothPagedView
removeView(cl);
} else {
// if this is the last non-custom content screen, convert it to the empty screen
- mWorkspaceScreens.put(EXTRA_EMPTY_SCREEN_RIGHT_ID, cl);
- mScreenOrder.add(EXTRA_EMPTY_SCREEN_RIGHT_ID);
+ mRemoveEmptyScreenRunnable = null;
+ mWorkspaceScreens.put(EXTRA_EMPTY_SCREEN_ID, cl);
+ mScreenOrder.add(EXTRA_EMPTY_SCREEN_ID);
}
}
@@ -864,6 +985,8 @@ public class Workspace extends SmoothPagedView
*/
void addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY,
boolean insert, boolean computeXYFromRank) {
+ //Reload settings
+ reloadSettings();
if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
if (getScreenWithId(screenId) == null) {
Log.e(TAG, "Skipping child, screenId " + screenId + " not found");
@@ -872,7 +995,7 @@ public class Workspace extends SmoothPagedView
return;
}
}
- if (screenId == EXTRA_EMPTY_SCREEN_LEFT_ID || screenId == EXTRA_EMPTY_SCREEN_RIGHT_ID) {
+ if (screenId == EXTRA_EMPTY_SCREEN_ID) {
// This should never happen
throw new RuntimeException("Screen id should not be EXTRA_EMPTY_SCREEN_ID");
}
@@ -921,7 +1044,9 @@ public class Workspace extends SmoothPagedView
}
// Get the canonical child id to uniquely represent this view in this screen
- int childId = LauncherModel.getCellLayoutChildId(container, screenId, x, y, spanX, spanY);
+ ItemInfo info = (ItemInfo) child.getTag();
+ int childId = mLauncher.getViewIdForItem(info);
+
boolean markCellsAsOccupied = !(child instanceof Folder);
if (!layout.addViewToCellLayout(child, insert ? 0 : -1, childId, lp, markCellsAsOccupied)) {
// TODO: This branch occurs when the workspace is adding views
@@ -1115,6 +1240,10 @@ public class Workspace extends SmoothPagedView
mDelayedResizeRunnable = null;
}
+ if (mDelayedSnapToPageRunnable != null) {
+ mDelayedSnapToPageRunnable.run();
+ mDelayedSnapToPageRunnable = null;
+ }
if (mStripScreensOnPageStopMoving) {
stripEmptyScreens();
mStripScreensOnPageStopMoving = false;
@@ -1124,16 +1253,18 @@ public class Workspace extends SmoothPagedView
@Override
protected void notifyPageSwitchListener() {
super.notifyPageSwitchListener();
- Launcher.setScreen(mCurrentPage);
+ Launcher.setScreen(getNextPage());
- if (hasCustomContent() && getNextPage() == 0 && !mCustomContentShowing) {
+ int ccIndex = getPageIndexForScreenId(CUSTOM_CONTENT_SCREEN_ID);
+ if (hasCustomContent() && getNextPage() == ccIndex && !mCustomContentShowing) {
mCustomContentShowing = true;
+
if (mCustomContentCallbacks != null) {
mCustomContentCallbacks.onShow();
mCustomContentShowTime = System.currentTimeMillis();
mLauncher.updateVoiceButtonProxyVisible(false);
}
- } else if (hasCustomContent() && getNextPage() != 0 && mCustomContentShowing) {
+ } else if (hasCustomContent() && getNextPage() != ccIndex && mCustomContentShowing) {
mCustomContentShowing = false;
if (mCustomContentCallbacks != null) {
mCustomContentCallbacks.onHide();
@@ -1151,10 +1282,44 @@ public class Workspace extends SmoothPagedView
}
protected void setWallpaperDimension() {
- String spKey = WallpaperCropActivity.getSharedPreferencesKey();
- SharedPreferences sp = mLauncher.getSharedPreferences(spKey, Context.MODE_PRIVATE);
- WallpaperPickerActivity.suggestWallpaperDimension(mLauncher.getResources(),
- sp, mLauncher.getWindowManager(), mWallpaperManager);
+ new AsyncTask<Void, Void, Void>() {
+ public Void doInBackground(Void ... args) {
+ String spKey = WallpaperCropActivity.getSharedPreferencesKey();
+ SharedPreferences sp =
+ mLauncher.getSharedPreferences(spKey, Context.MODE_MULTI_PROCESS);
+ LauncherWallpaperPickerActivity.suggestWallpaperDimension(mLauncher.getResources(),
+ sp, mLauncher.getWindowManager(), mWallpaperManager);
+ return null;
+ }
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
+ }
+
+ protected void snapToPage(int whichPage, Runnable r) {
+ snapToPage(whichPage, SLOW_PAGE_SNAP_ANIMATION_DURATION, r);
+ }
+
+ @Override
+ protected void snapToPage(int whichPage, int delta, int duration, boolean immediate,
+ TimeInterpolator interpolator) {
+ super.snapToPage(whichPage, delta, duration, immediate, interpolator);
+
+ // Trigger onCustomContentLaunch if we have just snapped to the custom page.
+ int customPageIndex = getPageIndexForScreenId(CUSTOM_CONTENT_SCREEN_ID);
+ if (hasCustomContent() && whichPage == customPageIndex && !mCustomContentShowing) {
+ if(!isInOverviewMode()) {
+ mCustomContentShowing = true;
+ // Start Google Now and register the gesture to return to Trebuchet
+ mLauncher.onCustomContentLaunch();
+ }
+ }
+ }
+
+ protected void snapToPage(int whichPage, int duration, Runnable r) {
+ if (mDelayedSnapToPageRunnable != null) {
+ mDelayedSnapToPageRunnable.run();
+ }
+ mDelayedSnapToPageRunnable = r;
+ snapToPage(whichPage, duration);
}
protected void snapToScreenId(long screenId, Runnable r) {
@@ -1228,9 +1393,11 @@ public class Workspace extends SmoothPagedView
}
// Exclude the leftmost page
- int firstIndex = numCustomPages() + (hasExtraEmptyScreenLeft() ? 1 : 0);
- // Exclude the last extra empty screen
- int lastIndex = getChildCount() - 1 - (hasExtraEmptyScreenRight() ? 1 : 0);
+ int emptyExtraPages = numEmptyScreensToIgnore();
+ int firstIndex = numCustomPages();
+ // Exclude the last extra empty screen (if we have > MIN_PARALLAX_PAGE_SPAN pages)
+ int lastIndex = getChildCount() - 1 - emptyExtraPages;
+
if (isLayoutRtl()) {
int temp = firstIndex;
firstIndex = lastIndex;
@@ -1245,16 +1412,21 @@ public class Workspace extends SmoothPagedView
// TODO: do different behavior if it's a live wallpaper?
// Sometimes the left parameter of the pages is animated during a layout transition;
// this parameter offsets it to keep the wallpaper from animating as well
- int offsetForLayoutTransitionAnimation = isLayoutRtl() ?
- getPageAt(getChildCount() - 1).getLeft() - getFirstChildLeft() : 0;
int adjustedScroll =
- getScrollX() - firstPageScrollX - offsetForLayoutTransitionAnimation;
+ getScrollX() - firstPageScrollX - getLayoutTransitionOffsetForPage(0);
float offset = Math.min(1, adjustedScroll / (float) scrollRange);
offset = Math.max(0, offset);
// Don't use up all the wallpaper parallax until you have at least
// MIN_PARALLAX_PAGE_SPAN pages
int numScrollingPages = getNumScreensExcludingEmptyAndCustom();
- int parallaxPageSpan = Math.max(MIN_PARALLAX_PAGE_SPAN, numScrollingPages - 1);
+ int parallaxPageSpan;
+ if (mWallpaperIsLiveWallpaper) {
+ parallaxPageSpan = numScrollingPages - 1;
+ } else {
+ parallaxPageSpan = Math.max(MIN_PARALLAX_PAGE_SPAN, numScrollingPages - 1);
+ }
+ mNumPagesForWallpaperParallax = parallaxPageSpan;
+
// On RTL devices, push the wallpaper offset to the right if we don't have enough
// pages (ie if numScrollingPages < MIN_PARALLAX_PAGE_SPAN)
int padding = isLayoutRtl() ? parallaxPageSpan - numScrollingPages + 1 : 0;
@@ -1262,8 +1434,17 @@ public class Workspace extends SmoothPagedView
}
}
+ private int numEmptyScreensToIgnore() {
+ int numScrollingPages = getChildCount() - numCustomPages();
+ if (numScrollingPages >= MIN_PARALLAX_PAGE_SPAN && hasExtraEmptyScreen()) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+
private int getNumScreensExcludingEmptyAndCustom() {
- int numScrollingPages = getChildCount() - numExtraEmptyScreens() - numCustomPages();
+ int numScrollingPages = getChildCount() - numEmptyScreensToIgnore() - numCustomPages();
return numScrollingPages;
}
@@ -1289,7 +1470,11 @@ public class Workspace extends SmoothPagedView
private void setWallpaperOffsetSteps() {
// Set wallpaper offset steps (1 / (number of screens - 1))
- mWallpaperManager.setWallpaperOffsetSteps(1.0f / (getChildCount() - 1), 1.0f);
+ float xOffset = 1.0f / mNumPagesForWallpaperParallax;
+ if (xOffset != mLastSetWallpaperOffsetSteps) {
+ mWallpaperManager.setWallpaperOffsetSteps(xOffset, 1.0f);
+ mLastSetWallpaperOffsetSteps = xOffset;
+ }
}
public void setFinalX(float x) {
@@ -1430,18 +1615,12 @@ public class Workspace extends SmoothPagedView
mState == State.NORMAL &&
!mIsSwitchingState &&
!isInOverscroll) {
- for (int i = 0; i < getChildCount(); i++) {
+ for (int i = numCustomPages(); i < getChildCount(); i++) {
CellLayout child = (CellLayout) getChildAt(i);
if (child != null) {
float scrollProgress = getScrollProgress(screenCenter, child, i);
float alpha = 1 - Math.abs(scrollProgress);
child.getShortcutsAndWidgets().setAlpha(alpha);
- if (!mIsDragOccuring && !mShowOutlines) {
- child.setBackgroundAlphaMultiplier(
- backgroundAlphaInterpolator(Math.abs(scrollProgress)));
- } else {
- child.setBackgroundAlphaMultiplier(1f);
- }
}
}
}
@@ -1584,7 +1763,7 @@ public class Workspace extends SmoothPagedView
cl.setOverscrollTransformsDirty(true);
}
} else {
- if (mOverscrollTransformsSet) {
+ if (mOverscrollTransformsSet && getChildCount() > 0) {
mOverscrollTransformsSet = false;
((CellLayout) getChildAt(0)).resetOverscrollTransforms();
((CellLayout) getChildAt(getChildCount() - 1)).resetOverscrollTransforms();
@@ -1631,6 +1810,31 @@ public class Workspace extends SmoothPagedView
} else {
mWallpaperOffset.syncWithScroll();
}
+
+ // Update wallpaper dimensions if they were changed since last onResume
+ // (we also always set the wallpaper dimensions in the constructor)
+ if (LauncherAppState.getInstance().hasWallpaperChangedSinceLastCheck()) {
+ setWallpaperDimension();
+ }
+ mWallpaperIsLiveWallpaper = mWallpaperManager.getWallpaperInfo() != null;
+ // Force the wallpaper offset steps to be set again, because another app might have changed
+ // them
+ mLastSetWallpaperOffsetSteps = 0f;
+
+ moveAwayFromCustomContentIfRequired();
+ }
+
+ public void moveAwayFromCustomContentIfRequired() {
+ // Never resume to the custom page if GEL integration is enabled.
+ int customPageIndex = getPageIndexForScreenId(CUSTOM_CONTENT_SCREEN_ID);
+ // mCustomContentShowing can be lost if the Activity is recreated,
+ // So make sure it is set to the right value.
+ mCustomContentShowing = mCustomContentShowing
+ || (customPageIndex == getCurrentPage()
+ && hasCustomContent());
+ if (mCustomContentShowing && mLauncher.isGelIntegrationEnabled()) {
+ moveToScreen((customPageIndex + 1), true);
+ }
}
@Override
@@ -1640,6 +1844,8 @@ public class Workspace extends SmoothPagedView
mWallpaperOffset.jumpToFinal();
}
super.onLayout(changed, left, top, right, bottom);
+
+ moveAwayFromCustomContentIfRequired();
}
@Override
@@ -1900,7 +2106,7 @@ public class Workspace extends SmoothPagedView
mDefaultScreenId = getScreenIdForPageIndex(getPageNearestToCenterOfScreen());
- exitOverviewMode(getPageNearestToCenterOfScreen(), true);
+ updateDefaultScreenButton();
SettingsProvider.get(mLauncher).edit()
.putLong(SettingsProvider.SETTINGS_UI_HOMESCREEN_DEFAULT_SCREEN_ID, mDefaultScreenId)
@@ -1972,6 +2178,9 @@ public class Workspace extends SmoothPagedView
}
private void enableOverviewMode(boolean enable, int snapPage, boolean animated) {
+ //Check to see if Settings need to taken
+ reloadSettings();
+
State finalState = Workspace.State.OVERVIEW;
if (!enable) {
finalState = Workspace.State.NORMAL;
@@ -1993,14 +2202,32 @@ public class Workspace extends SmoothPagedView
}
int getOverviewModeTranslationY() {
- int childHeight = getNormalChildHeight();
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+ Rect overviewBar = grid.getOverviewModeButtonBarRect();
+
+ 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;
+ }
+
+ float getOverviewModeScaleY() {
+ float childHeight = getNormalChildHeight();
int viewPortHeight = getViewportHeight();
- int scaledChildHeight = (int) (mOverviewModeShrinkFactor * childHeight);
- int offset = (viewPortHeight - scaledChildHeight) / 2;
- int offsetDelta = mOverviewModePageOffset - offset + mInsets.top;
+ Resources res = getResources();
+ int top = res.getDimensionPixelSize(R.dimen.overview_panel_top_padding);
+ top += res.getDimensionPixelSize(R.dimen.sliding_panel_padding);
+ top += res.getDimensionPixelSize(R.dimen.overview_scaling_padding);
- return offsetDelta;
+ float scaledChildHeight = viewPortHeight - top;
+
+ float scale = scaledChildHeight / childHeight;
+ return scale;
}
boolean shouldVoiceButtonProxyBeVisible() {
@@ -2069,16 +2296,16 @@ public class Workspace extends SmoothPagedView
mNewScale = 1.0f;
if (oldStateIsOverview) {
- disableFreeScroll(snapPage);
- } else if (stateIsOverview) {
- enableFreeScroll();
+ disableFreeScroll();
+ } else if (stateIsOverview){
+ updateFreescrollBounds();
}
if (state != State.NORMAL) {
if (stateIsSpringLoaded) {
mNewScale = mSpringLoadedShrinkFactor;
} else if (stateIsOverview) {
- mNewScale = mOverviewModeShrinkFactor;
+ mNewScale = getOverviewModeScaleY();
} else if (stateIsSmall){
mNewScale = mOverviewModeShrinkFactor - 0.3f;
}
@@ -2096,11 +2323,23 @@ public class Workspace extends SmoothPagedView
duration = getResources().getInteger(R.integer.config_appsCustomizeWorkspaceShrinkTime);
}
+ if (snapPage == -1) {
+ snapPage = getPageNearestToCenterOfScreen();
+ }
+ snapToPage(snapPage, duration, mZoomInInterpolator);
+
for (int i = 0; i < getChildCount(); i++) {
final CellLayout cl = (CellLayout) getChildAt(i);
- boolean isCurrentPage = (i == getNextPage());
+ boolean isCurrentPage = (i == snapPage);
float initialAlpha = cl.getShortcutsAndWidgets().getAlpha();
- float finalAlpha = stateIsSmall ? 0f : 1f;
+ float finalAlpha;
+ if (stateIsSmall) {
+ finalAlpha = 0f;
+ } else if (stateIsNormal && mWorkspaceFadeInAdjacentScreens) {
+ finalAlpha = (i == snapPage || i < numCustomPages()) ? 1f : 0f;
+ } else {
+ finalAlpha = 1f;
+ }
if (stateIsOverview) {
cl.setVisibility(VISIBLE);
@@ -2146,6 +2385,7 @@ public class Workspace extends SmoothPagedView
final View searchBar = mLauncher.getQsbBar();
final View overviewPanel = mLauncher.getOverviewPanel();
final View hotseat = mLauncher.getHotseat();
+ final View pageIndicator = getPageIndicator();
if (animated) {
anim.setDuration(duration);
LauncherViewPropertyAnimator scale = new LauncherViewPropertyAnimator(this);
@@ -2188,36 +2428,43 @@ public class Workspace extends SmoothPagedView
}
}
}
- ObjectAnimator pageIndicatorAlpha = null;
- if (getPageIndicator() != null) {
- pageIndicatorAlpha = ObjectAnimator.ofFloat(getPageIndicator(), "alpha",
- finalHotseatAndPageIndicatorAlpha);
- }
- ObjectAnimator hotseatAlpha = ObjectAnimator.ofFloat(hotseat, "alpha",
- finalHotseatAndPageIndicatorAlpha);
- ObjectAnimator searchBarAlpha = null;
- if (mShowSearchBar) {
- searchBarAlpha = ObjectAnimator.ofFloat(searchBar,
- "alpha", finalSearchBarAlpha);
+ Animator pageIndicatorAlpha = null;
+ if (pageIndicator != null) {
+ pageIndicatorAlpha = new LauncherViewPropertyAnimator(pageIndicator)
+ .alpha(finalHotseatAndPageIndicatorAlpha).withLayer();
+ pageIndicatorAlpha.addListener(new AlphaUpdateListener(pageIndicator));
+ } else {
+ // create a dummy animation so we don't need to do null checks later
+ pageIndicatorAlpha = ValueAnimator.ofFloat(0, 0);
}
- ObjectAnimator overviewPanelAlpha = ObjectAnimator.ofFloat(overviewPanel,
- "alpha", finalOverviewPanelAlpha);
- overviewPanelAlpha.addListener(new AlphaUpdateListener(overviewPanel));
+ Animator hotseatAlpha = new LauncherViewPropertyAnimator(hotseat)
+ .alpha(finalHotseatAndPageIndicatorAlpha).withLayer();
hotseatAlpha.addListener(new AlphaUpdateListener(hotseat));
+
+ Animator searchBarAlpha = new LauncherViewPropertyAnimator(searchBar)
+ .alpha(finalSearchBarAlpha).withLayer();
if (mShowSearchBar) searchBarAlpha.addListener(new AlphaUpdateListener(searchBar));
+ Animator overviewPanelAlpha = new LauncherViewPropertyAnimator(overviewPanel)
+ .alpha(finalOverviewPanelAlpha).withLayer();
+ overviewPanelAlpha.addListener(new AlphaUpdateListener(overviewPanel));
+
if (workspaceToOverview) {
+ pageIndicatorAlpha.setInterpolator(new DecelerateInterpolator(2));
hotseatAlpha.setInterpolator(new DecelerateInterpolator(2));
+ overviewPanelAlpha.setInterpolator(null);
} else if (overviewToWorkspace) {
+ pageIndicatorAlpha.setInterpolator(null);
+ hotseatAlpha.setInterpolator(null);
overviewPanelAlpha.setInterpolator(new DecelerateInterpolator(2));
}
+ searchBarAlpha.setInterpolator(null);
- if (getPageIndicator() != null) {
- pageIndicatorAlpha.addListener(new AlphaUpdateListener(getPageIndicator()));
- }
-
- anim.play(overviewPanelAlpha);
+ overviewPanel.setAlpha(finalOverviewPanelAlpha);
+ AlphaUpdateListener.updateVisibility(overviewPanel);
+ Animation animation = AnimationUtils.loadAnimation(mLauncher, R.anim.drop_down);
+ overviewPanel.startAnimation(animation);
anim.play(hotseatAlpha);
if (mShowSearchBar) anim.play(searchBarAlpha);
anim.play(pageIndicatorAlpha);
@@ -2227,9 +2474,9 @@ public class Workspace extends SmoothPagedView
AlphaUpdateListener.updateVisibility(overviewPanel);
hotseat.setAlpha(finalHotseatAndPageIndicatorAlpha);
AlphaUpdateListener.updateVisibility(hotseat);
- if (getPageIndicator() != null) {
- getPageIndicator().setAlpha(finalHotseatAndPageIndicatorAlpha);
- AlphaUpdateListener.updateVisibility(getPageIndicator());
+ if (pageIndicator != null) {
+ pageIndicator.setAlpha(finalHotseatAndPageIndicatorAlpha);
+ AlphaUpdateListener.updateVisibility(pageIndicator);
}
if (mShowSearchBar) {
searchBar.setAlpha(finalSearchBarAlpha);
@@ -2347,7 +2594,9 @@ public class Workspace extends SmoothPagedView
void hideCustomContentIfNecessary() {
boolean hide = mState != Workspace.State.NORMAL;
if (hide && hasCustomContent()) {
+ disableLayoutTransitions();
mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(INVISIBLE);
+ enableLayoutTransitions();
}
}
@@ -2363,6 +2612,11 @@ public class Workspace extends SmoothPagedView
final CellLayout cl = (CellLayout) getChildAt(i);
cl.setShortcutAndWidgetAlpha(1f);
}
+ } else {
+ for (int i = 0; i < numCustomPages(); i++) {
+ final CellLayout cl = (CellLayout) getChildAt(i);
+ cl.setShortcutAndWidgetAlpha(1f);
+ }
}
showCustomContentIfNecessary();
}
@@ -2548,10 +2802,21 @@ public class Workspace extends SmoothPagedView
if (child instanceof BubbleTextView) {
BubbleTextView icon = (BubbleTextView) child;
icon.clearPressedOrFocusedBackground();
+ } else if (child instanceof FolderIcon) {
+ // Dismiss the folder cling if we haven't already
+ mLauncher.getLauncherClings().markFolderClingDismissed();
}
- mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
+ if (child.getTag() == null || !(child.getTag() instanceof ItemInfo)) {
+ String msg = "Drag started with a view that has no tag set. This "
+ + "will cause a crash (issue 11627249) down the line. "
+ + "View: " + child + " tag: " + child.getTag();
+ throw new IllegalStateException(msg);
+ }
+
+ DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale);
+ dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());
if (child.getParent() instanceof ShortcutAndWidgetContainer) {
mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent();
@@ -2623,17 +2888,18 @@ public class Workspace extends SmoothPagedView
mTargetCell);
float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0],
mDragViewVisualCenter[1], mTargetCell);
- if (willCreateUserFolder((ItemInfo) d.dragInfo, dropTargetLayout,
- mTargetCell, distance, true)) {
+ if (mCreateUserFolderOnDrop && willCreateUserFolder((ItemInfo) d.dragInfo,
+ dropTargetLayout, mTargetCell, distance, true)) {
return true;
}
- if (willAddToExistingUserFolder((ItemInfo) d.dragInfo, dropTargetLayout,
- mTargetCell, distance)) {
+
+ if (mAddToExistingFolderOnDrop && willAddToExistingUserFolder((ItemInfo) d.dragInfo,
+ dropTargetLayout, mTargetCell, distance)) {
return true;
}
int[] resultSpan = new int[2];
- mTargetCell = dropTargetLayout.createArea((int) mDragViewVisualCenter[0],
+ mTargetCell = dropTargetLayout.performReorder((int) mDragViewVisualCenter[0],
(int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
null, mTargetCell, resultSpan, CellLayout.MODE_ACCEPT_DROP);
boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;
@@ -2648,8 +2914,8 @@ public class Workspace extends SmoothPagedView
}
long screenId = getIdForScreen(dropTargetLayout);
- if (screenId == EXTRA_EMPTY_SCREEN_LEFT_ID || screenId == EXTRA_EMPTY_SCREEN_RIGHT_ID) {
- commitExtraEmptyScreen(screenId);
+ if (screenId == EXTRA_EMPTY_SCREEN_ID) {
+ commitExtraEmptyScreen();
}
return true;
@@ -2831,13 +3097,13 @@ public class Workspace extends SmoothPagedView
// cell also contains a shortcut, then create a folder with the two shortcuts.
if (!mInScrollArea && createUserFolderIfNecessary(cell, container,
dropTargetLayout, mTargetCell, distance, false, d.dragView, null)) {
- stripEmptyScreens();
+ removeExtraEmptyScreen(true, null, 0, true);
return;
}
if (addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell,
distance, d, false)) {
- stripEmptyScreens();
+ removeExtraEmptyScreen(true, null, 0, true);
return;
}
@@ -2852,7 +3118,7 @@ public class Workspace extends SmoothPagedView
}
int[] resultSpan = new int[2];
- mTargetCell = dropTargetLayout.createArea((int) mDragViewVisualCenter[0],
+ mTargetCell = dropTargetLayout.performReorder((int) mDragViewVisualCenter[0],
(int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, cell,
mTargetCell, resultSpan, CellLayout.MODE_ON_DROP);
@@ -2892,8 +3158,6 @@ public class Workspace extends SmoothPagedView
lp.cellHSpan = item.spanX;
lp.cellVSpan = item.spanY;
lp.isLockedToGrid = true;
- cell.setId(LauncherModel.getCellLayoutChildId(container, mDragInfo.screenId,
- mTargetCell[0], mTargetCell[1], mDragInfo.spanX, mDragInfo.spanY));
if (container != LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
cell instanceof LauncherAppWidgetHostView) {
@@ -2946,7 +3210,7 @@ public class Workspace extends SmoothPagedView
if (finalResizeRunnable != null) {
finalResizeRunnable.run();
}
- stripEmptyScreens();
+ removeExtraEmptyScreen(true, null, 0, true);
}
};
mAnimatingViewIntoPlace = true;
@@ -3035,13 +3299,11 @@ public class Workspace extends SmoothPagedView
display.getCurrentSizeRange(smallestSize, largestSize);
int countX = (int) grid.numColumns;
int countY = (int) grid.numRows;
- int constrainedLongEdge = largestSize.y;
- int constrainedShortEdge = smallestSize.y;
if (orientation == CellLayout.LANDSCAPE) {
if (mLandscapeCellLayoutMetrics == null) {
Rect padding = grid.getWorkspacePadding(CellLayout.LANDSCAPE);
- int width = constrainedLongEdge - padding.left - padding.right;
- int height = constrainedShortEdge - padding.top - padding.bottom;
+ int width = largestSize.x - padding.left - padding.right;
+ int height = smallestSize.y - padding.top - padding.bottom;
mLandscapeCellLayoutMetrics = new Rect();
mLandscapeCellLayoutMetrics.set(
grid.calculateCellWidth(width, countX),
@@ -3051,8 +3313,8 @@ public class Workspace extends SmoothPagedView
} else if (orientation == CellLayout.PORTRAIT) {
if (mPortraitCellLayoutMetrics == null) {
Rect padding = grid.getWorkspacePadding(CellLayout.PORTRAIT);
- int width = constrainedShortEdge - padding.left - padding.right;
- int height = constrainedLongEdge - padding.top - padding.bottom;
+ int width = smallestSize.x - padding.left - padding.right;
+ int height = largestSize.y - padding.top - padding.bottom;
mPortraitCellLayoutMetrics = new Rect();
mPortraitCellLayoutMetrics.set(
grid.calculateCellWidth(width, countX),
@@ -3437,6 +3699,11 @@ public class Workspace extends SmoothPagedView
&& !mReorderAlarm.alarmPending() && (mLastReorderX != reorderX ||
mLastReorderY != reorderY)) {
+ int[] resultSpan = new int[2];
+ mDragTargetLayout.performReorder((int) mDragViewVisualCenter[0],
+ (int) mDragViewVisualCenter[1], minSpanX, minSpanY, item.spanX, item.spanY,
+ child, mTargetCell, resultSpan, CellLayout.MODE_SHOW_REORDER_HINT);
+
// Otherwise, if we aren't adding to or creating a folder and there's no pending
// reorder, then we schedule a reorder
ReorderAlarmListener listener = new ReorderAlarmListener(mDragViewVisualCenter,
@@ -3541,7 +3808,7 @@ public class Workspace extends SmoothPagedView
mLastReorderX = mTargetCell[0];
mLastReorderY = mTargetCell[1];
- mTargetCell = mDragTargetLayout.createArea((int) mDragViewVisualCenter[0],
+ mTargetCell = mDragTargetLayout.performReorder((int) mDragViewVisualCenter[0],
(int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
child, mTargetCell, resultSpan, CellLayout.MODE_DRAG_OVER);
@@ -3597,7 +3864,13 @@ public class Workspace extends SmoothPagedView
final Runnable exitSpringLoadedRunnable = new Runnable() {
@Override
public void run() {
- mLauncher.exitSpringLoadedDragModeDelayed(true, false, null);
+ removeExtraEmptyScreen(false, new Runnable() {
+ @Override
+ public void run() {
+ mLauncher.exitSpringLoadedDragModeDelayed(true,
+ Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
+ }
+ });
}
};
@@ -3645,7 +3918,7 @@ public class Workspace extends SmoothPagedView
minSpanY = item.minSpanY;
}
int[] resultSpan = new int[2];
- mTargetCell = cellLayout.createArea((int) mDragViewVisualCenter[0],
+ mTargetCell = cellLayout.performReorder((int) mDragViewVisualCenter[0],
(int) mDragViewVisualCenter[1], minSpanX, minSpanY, info.spanX, info.spanY,
null, mTargetCell, resultSpan, CellLayout.MODE_ON_DROP_EXTERNAL);
@@ -3738,7 +4011,7 @@ public class Workspace extends SmoothPagedView
if (touchXY != null) {
// when dragging and dropping, just find the closest free spot
- mTargetCell = cellLayout.createArea((int) mDragViewVisualCenter[0],
+ mTargetCell = cellLayout.performReorder((int) mDragViewVisualCenter[0],
(int) mDragViewVisualCenter[1], 1, 1, 1, 1,
null, mTargetCell, null, CellLayout.MODE_ON_DROP_EXTERNAL);
} else {
@@ -3759,7 +4032,7 @@ public class Workspace extends SmoothPagedView
// the correct final location.
setFinalTransitionTransform(cellLayout);
mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, view,
- exitSpringLoadedRunnable);
+ exitSpringLoadedRunnable, this);
resetTransitionTransform(cellLayout);
}
}
@@ -3834,7 +4107,7 @@ public class Workspace extends SmoothPagedView
external, scalePreview);
Resources res = mLauncher.getResources();
- int duration = res.getInteger(R.integer.config_dropAnimMaxDuration) - 200;
+ final int duration = res.getInteger(R.integer.config_dropAnimMaxDuration) - 200;
// In the case where we've prebound the widget, we remove it from the DragLayer
if (finalView instanceof AppWidgetHostView && external) {
@@ -3910,8 +4183,8 @@ public class Workspace extends SmoothPagedView
return mDragInfo;
}
- public int getRestorePage() {
- return getNextPage() - numCustomPages() - (hasExtraEmptyScreenLeft() ? 1 : 0);
+ public int getCurrentPageOffsetFromCustomContent() {
+ return getNextPage() - numCustomPages();
}
/**
@@ -3932,7 +4205,6 @@ public class Workspace extends SmoothPagedView
// hardware layers on children are enabled on startup, but should be disabled until
// needed
updateChildrenLayersEnabled(false);
- setWallpaperDimension();
}
/**
@@ -3942,11 +4214,11 @@ public class Workspace extends SmoothPagedView
final boolean isFlingToDelete, final boolean success) {
if (mDeferDropAfterUninstall) {
mDeferredAction = new Runnable() {
- public void run() {
- onDropCompleted(target, d, isFlingToDelete, success);
- mDeferredAction = null;
- }
- };
+ public void run() {
+ onDropCompleted(target, d, isFlingToDelete, success);
+ mDeferredAction = null;
+ }
+ };
return;
}
@@ -3964,7 +4236,7 @@ public class Workspace extends SmoothPagedView
// If we move the item to anything not on the Workspace, check if any empty
// screens need to be removed. If we dropped back on the workspace, this will
// be done post drop animation.
- stripEmptyScreens();
+ removeExtraEmptyScreen(true, null, 0, true);
}
} else if (mDragInfo != null && target != null && (!(target instanceof InfoDropTarget))) {
CellLayout cellLayout;
@@ -3973,7 +4245,13 @@ public class Workspace extends SmoothPagedView
} else {
cellLayout = getScreenWithId(mDragInfo.screenId);
}
- cellLayout.onDropChild(mDragInfo.cell);
+ if (cellLayout == null && LauncherAppState.isDogfoodBuild()) {
+ throw new RuntimeException("Invalid state: cellLayout == null in "
+ + "Workspace#onDropCompleted. Please file a bug. ");
+ }
+ if (cellLayout != null) {
+ cellLayout.onDropChild(mDragInfo.cell);
+ }
}
if ((d.cancelled || target instanceof InfoDropTarget || (beingCalledAfterUninstall && !mUninstallSuccessful))
&& mDragInfo.cell != null) {
@@ -4144,11 +4422,26 @@ public class Workspace extends SmoothPagedView
}
@Override
+ public float getIntrinsicIconScaleFactor() {
+ return 1f;
+ }
+
+ @Override
public boolean supportsFlingToDelete() {
return true;
}
@Override
+ public boolean supportsAppInfoDropTarget() {
+ return false;
+ }
+
+ @Override
+ public boolean supportsDeleteDropTarget() {
+ return true;
+ }
+
+ @Override
public void onFlingToDelete(DragObject d, int x, int y, PointF vec) {
// Do nothing
}
@@ -4491,29 +4784,53 @@ public class Workspace extends SmoothPagedView
stripEmptyScreens();
}
+ private void updateShortcut(HashMap<ComponentName, AppInfo> appsMap, ItemInfo info,
+ View child) {
+ if (info.getIntent() == null) {
+ // it's the all-apps shortcut, don't crash!
+ return;
+ }
+ ComponentName cn = info.getIntent().getComponent();
+ if (info.getRestoredIntent() != null) {
+ cn = info.getRestoredIntent().getComponent();
+ }
+ if (cn != null) {
+ AppInfo appInfo = appsMap.get(cn);
+ if ((appInfo != null) && LauncherModel.isShortcutInfoUpdateable(info)) {
+ ShortcutInfo shortcutInfo = (ShortcutInfo) info;
+ BubbleTextView shortcut = (BubbleTextView) child;
+ shortcutInfo.restore();
+ shortcutInfo.updateIcon(mIconCache);
+ shortcutInfo.title = appInfo.title.toString();
+ shortcut.applyFromShortcutInfo(shortcutInfo, mIconCache);
+ }
+ }
+ }
+
void updateShortcuts(ArrayList<AppInfo> apps) {
+ // Create a map of the apps to test against
+ final HashMap<ComponentName, AppInfo> appsMap = new HashMap<ComponentName, AppInfo>();
+ for (AppInfo ai : apps) {
+ appsMap.put(ai.componentName, ai);
+ }
+
ArrayList<ShortcutAndWidgetContainer> childrenLayouts = getAllShortcutAndWidgetContainers();
for (ShortcutAndWidgetContainer layout: childrenLayouts) {
- int childCount = layout.getChildCount();
- for (int j = 0; j < childCount; j++) {
- final View view = layout.getChildAt(j);
- Object tag = view.getTag();
-
- if (LauncherModel.isShortcutInfoUpdateable((ItemInfo) tag)) {
- ShortcutInfo info = (ShortcutInfo) tag;
-
- final Intent intent = info.intent;
- final ComponentName name = intent.getComponent();
- final int appCount = apps.size();
- for (int k = 0; k < appCount; k++) {
- AppInfo app = apps.get(k);
- if (app.componentName.equals(name)) {
- BubbleTextView shortcut = (BubbleTextView) view;
- info.updateIcon(mIconCache);
- info.title = app.title.toString();
- shortcut.applyFromShortcutInfo(info, mIconCache);
- }
+ // Update all the children shortcuts
+ final HashMap<ItemInfo, View> children = new HashMap<ItemInfo, View>();
+ for (int j = 0; j < layout.getChildCount(); j++) {
+ View v = layout.getChildAt(j);
+ ItemInfo info = (ItemInfo) v.getTag();
+ if (info instanceof FolderInfo && v instanceof FolderIcon) {
+ FolderIcon folder = (FolderIcon) v;
+ ArrayList<View> folderChildren = folder.getFolder().getItemsInReadingOrder();
+ for (View fv : folderChildren) {
+ info = (ItemInfo) fv.getTag();
+ updateShortcut(appsMap, info, fv);
}
+ folder.invalidate();
+ } else if (info instanceof ShortcutInfo) {
+ updateShortcut(appsMap, info, v);
}
}
}
@@ -4534,7 +4851,15 @@ public class Workspace extends SmoothPagedView
}
void moveToDefaultScreen(boolean animate) {
- moveToScreen(getPageIndexForScreenId(mDefaultScreenId), animate);
+ // Do not use the custom page or index -1 as default,
+ // if GEL integration is enabled.
+ int idx = getPageIndexForScreenId(mDefaultScreenId);
+ int ccIndex = getPageIndexForScreenId(CUSTOM_CONTENT_SCREEN_ID);
+ if(hasCustomContent() && (idx == ccIndex || idx == -1)
+ && mLauncher.isGelIntegrationEnabled()) {
+ idx = 1;
+ }
+ moveToScreen(idx, animate);
}
void moveToCustomContentScreen(boolean animate) {
@@ -4550,12 +4875,13 @@ public class Workspace extends SmoothPagedView
child.requestFocus();
}
}
+ exitWidgetResizeMode();
}
@Override
protected PageIndicator.PageMarkerResources getPageIndicatorMarker(int pageIndex) {
long screenId = getScreenIdForPageIndex(pageIndex);
- if (screenId == EXTRA_EMPTY_SCREEN_LEFT_ID || screenId == EXTRA_EMPTY_SCREEN_RIGHT_ID) {
+ if (screenId == EXTRA_EMPTY_SCREEN_ID) {
int count = mScreenOrder.size() - numCustomPages();
if (count > 1) {
return new PageIndicator.PageMarkerResources(R.drawable.ic_pageindicator_current,
@@ -4592,4 +4918,45 @@ public class Workspace extends SmoothPagedView
public void getLocationInDragLayer(int[] loc) {
mLauncher.getDragLayer().getLocationInDragLayer(this, loc);
}
+
+ private void reloadSettings() {
+ mShowSearchBar = SettingsProvider.getBoolean(mLauncher, SettingsProvider.SETTINGS_UI_HOMESCREEN_SEARCH,
+ R.bool.preferences_interface_homescreen_search_default);
+ mShowOutlines = SettingsProvider.getBoolean(mLauncher,
+ SettingsProvider.SETTINGS_UI_HOMESCREEN_SCROLLING_PAGE_OUTLINES,
+ R.bool.preferences_interface_homescreen_scrolling_page_outlines_default);
+ mHideIconLabels = SettingsProvider.getBoolean(mLauncher,
+ SettingsProvider.SETTINGS_UI_HOMESCREEN_HIDE_ICON_LABELS,
+ R.bool.preferences_interface_homescreen_hide_icon_labels_default);
+ mWorkspaceFadeInAdjacentScreens = SettingsProvider.getBoolean(mLauncher,
+ SettingsProvider.SETTINGS_UI_HOMESCREEN_SCROLLING_FADE_ADJACENT,
+ R.bool.preferences_interface_homescreen_scrolling_fade_adjacent_default);
+ TransitionEffect.setFromString(this, SettingsProvider.getString(mLauncher,
+ SettingsProvider.SETTINGS_UI_HOMESCREEN_SCROLLING_TRANSITION_EFFECT,
+ R.string.preferences_interface_homescreen_scrolling_transition_effect));
+
+ mScrollWallpaper = SettingsProvider.getBoolean(mLauncher,
+ SettingsProvider.SETTINGS_UI_HOMESCREEN_SCROLLING_WALLPAPER_SCROLL,
+ R.bool.preferences_interface_homescreen_scrolling_wallpaper_scroll_default);
+
+ if (!mScrollWallpaper) {
+ if (mWindowToken != null) mWallpaperManager.setWallpaperOffsets(mWindowToken, 0f, 0.5f);
+ } else {
+ mWallpaperOffset.syncWithScroll();
+ }
+ }
+
+ public boolean getShowSearchBar() {
+ return mShowSearchBar;
+ }
+
+ public boolean getHideIconLables() {
+ return mHideIconLabels;
+ }
+
+ @Override
+ public void scrollTo(int x, int y) {
+ mEnforceRealBounds = isInOverviewMode();
+ super.scrollTo(x, y);
+ }
}
diff --git a/src/com/android/launcher3/list/AutoScrollListView.java b/src/com/android/launcher3/list/AutoScrollListView.java
new file mode 100644
index 000000000..66336bc71
--- /dev/null
+++ b/src/com/android/launcher3/list/AutoScrollListView.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.list;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.ListView;
+
+/**
+ * A ListView that can be asked to scroll (smoothly or otherwise) to a specific
+ * position. This class takes advantage of similar functionality that exists
+ * in {@link ListView} and enhances it.
+ */
+public class AutoScrollListView extends ListView {
+
+ /**
+ * Position the element at about 1/3 of the list height
+ */
+ private static final float PREFERRED_SELECTION_OFFSET_FROM_TOP = 0.33f;
+
+ private int mRequestedScrollPosition = -1;
+ private boolean mSmoothScrollRequested;
+
+ public AutoScrollListView(Context context) {
+ super(context);
+ }
+
+ public AutoScrollListView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public AutoScrollListView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ /**
+ * Brings the specified position to view by optionally performing a jump-scroll maneuver:
+ * first it jumps to some position near the one requested and then does a smooth
+ * scroll to the requested position. This creates an impression of full smooth
+ * scrolling without actually traversing the entire list. If smooth scrolling is
+ * not requested, instantly positions the requested item at a preferred offset.
+ */
+ public void requestPositionToScreen(int position, boolean smoothScroll) {
+ mRequestedScrollPosition = position;
+ mSmoothScrollRequested = smoothScroll;
+ requestLayout();
+ }
+
+ @Override
+ protected void layoutChildren() {
+ super.layoutChildren();
+ if (mRequestedScrollPosition == -1) {
+ return;
+ }
+
+ final int position = mRequestedScrollPosition;
+ mRequestedScrollPosition = -1;
+
+ int firstPosition = getFirstVisiblePosition() + 1;
+ int lastPosition = getLastVisiblePosition();
+ if (position >= firstPosition && position <= lastPosition) {
+ return; // Already on screen
+ }
+
+ final int offset = (int) (getHeight() * PREFERRED_SELECTION_OFFSET_FROM_TOP);
+ if (!mSmoothScrollRequested) {
+ setSelectionFromTop(position, offset);
+
+ // Since we have changed the scrolling position, we need to redo child layout
+ // Calling "requestLayout" in the middle of a layout pass has no effect,
+ // so we call layoutChildren explicitly
+ super.layoutChildren();
+
+ } else {
+ // We will first position the list a couple of screens before or after
+ // the new selection and then scroll smoothly to it.
+ int twoScreens = (lastPosition - firstPosition) * 2;
+ int preliminaryPosition;
+ if (position < firstPosition) {
+ preliminaryPosition = position + twoScreens;
+ if (preliminaryPosition >= getCount()) {
+ preliminaryPosition = getCount() - 1;
+ }
+ if (preliminaryPosition < firstPosition) {
+ setSelection(preliminaryPosition);
+ super.layoutChildren();
+ }
+ } else {
+ preliminaryPosition = position - twoScreens;
+ if (preliminaryPosition < 0) {
+ preliminaryPosition = 0;
+ }
+ if (preliminaryPosition > lastPosition) {
+ setSelection(preliminaryPosition);
+ super.layoutChildren();
+ }
+ }
+
+
+ smoothScrollToPositionFromTop(position, offset);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/list/CompositeCursorAdapter.java b/src/com/android/launcher3/list/CompositeCursorAdapter.java
new file mode 100644
index 000000000..b1ddb67fb
--- /dev/null
+++ b/src/com/android/launcher3/list/CompositeCursorAdapter.java
@@ -0,0 +1,532 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.list;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+
+import java.util.ArrayList;
+
+/**
+ * A general purpose adapter that is composed of multiple cursors. It just
+ * appends them in the order they are added.
+ */
+public abstract class CompositeCursorAdapter extends BaseAdapter {
+
+ private static final int INITIAL_CAPACITY = 2;
+
+ public static class Partition {
+ boolean showIfEmpty;
+ boolean hasHeader;
+
+ Cursor cursor;
+ int idColumnIndex;
+ int count;
+
+ public Partition(boolean showIfEmpty, boolean hasHeader) {
+ this.showIfEmpty = showIfEmpty;
+ this.hasHeader = hasHeader;
+ }
+
+ /**
+ * True if the directory should be shown even if no contacts are found.
+ */
+ public boolean getShowIfEmpty() {
+ return showIfEmpty;
+ }
+
+ public boolean getHasHeader() {
+ return hasHeader;
+ }
+ }
+
+ private final Context mContext;
+ private ArrayList<Partition> mPartitions;
+ private int mCount = 0;
+ private boolean mCacheValid = true;
+ private boolean mNotificationsEnabled = true;
+ private boolean mNotificationNeeded;
+
+ public CompositeCursorAdapter(Context context) {
+ this(context, INITIAL_CAPACITY);
+ }
+
+ public CompositeCursorAdapter(Context context, int initialCapacity) {
+ mContext = context;
+ mPartitions = new ArrayList<Partition>();
+ }
+
+ public Context getContext() {
+ return mContext;
+ }
+
+ /**
+ * Registers a partition. The cursor for that partition can be set later.
+ * Partitions should be added in the order they are supposed to appear in the
+ * list.
+ */
+ public void addPartition(boolean showIfEmpty, boolean hasHeader) {
+ addPartition(new Partition(showIfEmpty, hasHeader));
+ }
+
+ public void addPartition(Partition partition) {
+ mPartitions.add(partition);
+ invalidate();
+ notifyDataSetChanged();
+ }
+
+ public void addPartition(int location, Partition partition) {
+ mPartitions.add(location, partition);
+ invalidate();
+ notifyDataSetChanged();
+ }
+
+ public void removePartition(int partitionIndex) {
+ Cursor cursor = mPartitions.get(partitionIndex).cursor;
+ if (cursor != null && !cursor.isClosed()) {
+ cursor.close();
+ }
+ mPartitions.remove(partitionIndex);
+ invalidate();
+ notifyDataSetChanged();
+ }
+
+ /**
+ * Removes cursors for all partitions.
+ */
+ // TODO: Is this really what this is supposed to do? Just remove the cursors? Not close them?
+ // Not remove the partitions themselves? Isn't this leaking?
+
+ public void clearPartitions() {
+ for (Partition partition : mPartitions) {
+ partition.cursor = null;
+ }
+ invalidate();
+ notifyDataSetChanged();
+ }
+
+ /**
+ * Closes all cursors and removes all partitions.
+ */
+ public void close() {
+ for (Partition partition : mPartitions) {
+ Cursor cursor = partition.cursor;
+ if (cursor != null && !cursor.isClosed()) {
+ cursor.close();
+ }
+ }
+ mPartitions.clear();
+ invalidate();
+ notifyDataSetChanged();
+ }
+
+ public void setHasHeader(int partitionIndex, boolean flag) {
+ mPartitions.get(partitionIndex).hasHeader = flag;
+ invalidate();
+ }
+
+ public void setShowIfEmpty(int partitionIndex, boolean flag) {
+ mPartitions.get(partitionIndex).showIfEmpty = flag;
+ invalidate();
+ }
+
+ public Partition getPartition(int partitionIndex) {
+ return mPartitions.get(partitionIndex);
+ }
+
+ protected void invalidate() {
+ mCacheValid = false;
+ }
+
+ public int getPartitionCount() {
+ return mPartitions.size();
+ }
+
+ protected void ensureCacheValid() {
+ if (mCacheValid) {
+ return;
+ }
+
+ mCount = 0;
+ for (Partition partition : mPartitions) {
+ Cursor cursor = partition.cursor;
+ int count = cursor != null ? cursor.getCount() : 0;
+ if (partition.hasHeader) {
+ if (count != 0 || partition.showIfEmpty) {
+ count++;
+ }
+ }
+ partition.count = count;
+ mCount += count;
+ }
+
+ mCacheValid = true;
+ }
+
+ /**
+ * Returns true if the specified partition was configured to have a header.
+ */
+ public boolean hasHeader(int partition) {
+ return mPartitions.get(partition).hasHeader;
+ }
+
+ /**
+ * Returns the total number of list items in all partitions.
+ */
+ public int getCount() {
+ ensureCacheValid();
+ return mCount;
+ }
+
+ /**
+ * Returns the cursor for the given partition
+ */
+ public Cursor getCursor(int partition) {
+ return mPartitions.get(partition).cursor;
+ }
+
+ /**
+ * Changes the cursor for an individual partition.
+ */
+ public void changeCursor(int partition, Cursor cursor) {
+ Cursor prevCursor = mPartitions.get(partition).cursor;
+ if (prevCursor != cursor) {
+ if (prevCursor != null && !prevCursor.isClosed()) {
+ prevCursor.close();
+ }
+ mPartitions.get(partition).cursor = cursor;
+ if (cursor != null) {
+ mPartitions.get(partition).idColumnIndex = cursor.getColumnIndex("_id");
+ }
+ invalidate();
+ notifyDataSetChanged();
+ }
+ }
+
+ /**
+ * Returns true if the specified partition has no cursor or an empty cursor.
+ */
+ public boolean isPartitionEmpty(int partition) {
+ Cursor cursor = mPartitions.get(partition).cursor;
+ return cursor == null || cursor.getCount() == 0;
+ }
+
+ /**
+ * Given a list position, returns the index of the corresponding partition.
+ */
+ public int getPartitionForPosition(int position) {
+ ensureCacheValid();
+ int start = 0;
+ for (int i = 0, n = mPartitions.size(); i < n; i++) {
+ int end = start + mPartitions.get(i).count;
+ if (position >= start && position < end) {
+ return i;
+ }
+ start = end;
+ }
+ return -1;
+ }
+
+ /**
+ * Given a list position, return the offset of the corresponding item in its
+ * partition. The header, if any, will have offset -1.
+ */
+ public int getOffsetInPartition(int position) {
+ ensureCacheValid();
+ int start = 0;
+ for (Partition partition : mPartitions) {
+ int end = start + partition.count;
+ if (position >= start && position < end) {
+ int offset = position - start;
+ if (partition.hasHeader) {
+ offset--;
+ }
+ return offset;
+ }
+ start = end;
+ }
+ return -1;
+ }
+
+ /**
+ * Returns the first list position for the specified partition.
+ */
+ public int getPositionForPartition(int partition) {
+ ensureCacheValid();
+ int position = 0;
+ for (int i = 0; i < partition; i++) {
+ position += mPartitions.get(i).count;
+ }
+ return position;
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return getItemViewTypeCount() + 1;
+ }
+
+ /**
+ * Returns the overall number of item view types across all partitions. An
+ * implementation of this method needs to ensure that the returned count is
+ * consistent with the values returned by {@link #getItemViewType(int,int)}.
+ */
+ public int getItemViewTypeCount() {
+ return 1;
+ }
+
+ /**
+ * Returns the view type for the list item at the specified position in the
+ * specified partition.
+ */
+ protected int getItemViewType(int partition, int position) {
+ return 1;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ ensureCacheValid();
+ int start = 0;
+ for (int i = 0, n = mPartitions.size(); i < n; i++) {
+ int end = start + mPartitions.get(i).count;
+ if (position >= start && position < end) {
+ int offset = position - start;
+ if (mPartitions.get(i).hasHeader) {
+ offset--;
+ }
+ if (offset == -1) {
+ return IGNORE_ITEM_VIEW_TYPE;
+ } else {
+ return getItemViewType(i, offset);
+ }
+ }
+ start = end;
+ }
+
+ throw new ArrayIndexOutOfBoundsException(position);
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ ensureCacheValid();
+ int start = 0;
+ for (int i = 0, n = mPartitions.size(); i < n; i++) {
+ int end = start + mPartitions.get(i).count;
+ if (position >= start && position < end) {
+ int offset = position - start;
+ if (mPartitions.get(i).hasHeader) {
+ offset--;
+ }
+ View view;
+ if (offset == -1) {
+ view = getHeaderView(i, mPartitions.get(i).cursor, convertView, parent);
+ } else {
+ if (!mPartitions.get(i).cursor.moveToPosition(offset)) {
+ throw new IllegalStateException("Couldn't move cursor to position "
+ + offset);
+ }
+ view = getView(i, mPartitions.get(i).cursor, offset, convertView, parent);
+ }
+ if (view == null) {
+ throw new NullPointerException("View should not be null, partition: " + i
+ + " position: " + offset);
+ }
+ return view;
+ }
+ start = end;
+ }
+
+ throw new ArrayIndexOutOfBoundsException(position);
+ }
+
+ /**
+ * Returns the header view for the specified partition, creating one if needed.
+ */
+ protected View getHeaderView(int partition, Cursor cursor, View convertView,
+ ViewGroup parent) {
+ View view = convertView != null
+ ? convertView
+ : newHeaderView(mContext, partition, cursor, parent);
+ bindHeaderView(view, partition, cursor);
+ return view;
+ }
+
+ /**
+ * Creates the header view for the specified partition.
+ */
+ protected View newHeaderView(Context context, int partition, Cursor cursor,
+ ViewGroup parent) {
+ return null;
+ }
+
+ /**
+ * Binds the header view for the specified partition.
+ */
+ protected void bindHeaderView(View view, int partition, Cursor cursor) {
+ }
+
+ /**
+ * Returns an item view for the specified partition, creating one if needed.
+ */
+ protected View getView(int partition, Cursor cursor, int position, View convertView,
+ ViewGroup parent) {
+ View view;
+ if (convertView != null) {
+ view = convertView;
+ } else {
+ view = newView(mContext, partition, cursor, position, parent);
+ }
+ bindView(view, partition, cursor, position);
+ return view;
+ }
+
+ /**
+ * Creates an item view for the specified partition and position. Position
+ * corresponds directly to the current cursor position.
+ */
+ protected abstract View newView(Context context, int partition, Cursor cursor, int position,
+ ViewGroup parent);
+
+ /**
+ * Binds an item view for the specified partition and position. Position
+ * corresponds directly to the current cursor position.
+ */
+ protected abstract void bindView(View v, int partition, Cursor cursor, int position);
+
+ /**
+ * Returns a pre-positioned cursor for the specified list position.
+ */
+ public Object getItem(int position) {
+ ensureCacheValid();
+ int start = 0;
+ for (Partition mPartition : mPartitions) {
+ int end = start + mPartition.count;
+ if (position >= start && position < end) {
+ int offset = position - start;
+ if (mPartition.hasHeader) {
+ offset--;
+ }
+ if (offset == -1) {
+ return null;
+ }
+ Cursor cursor = mPartition.cursor;
+ cursor.moveToPosition(offset);
+ return cursor;
+ }
+ start = end;
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the item ID for the specified list position.
+ */
+ public long getItemId(int position) {
+ ensureCacheValid();
+ int start = 0;
+ for (Partition mPartition : mPartitions) {
+ int end = start + mPartition.count;
+ if (position >= start && position < end) {
+ int offset = position - start;
+ if (mPartition.hasHeader) {
+ offset--;
+ }
+ if (offset == -1) {
+ return 0;
+ }
+ if (mPartition.idColumnIndex == -1) {
+ return 0;
+ }
+
+ Cursor cursor = mPartition.cursor;
+ if (cursor == null || cursor.isClosed() || !cursor.moveToPosition(offset)) {
+ return 0;
+ }
+ return cursor.getLong(mPartition.idColumnIndex);
+ }
+ start = end;
+ }
+
+ return 0;
+ }
+
+ /**
+ * Returns false if any partition has a header.
+ */
+ @Override
+ public boolean areAllItemsEnabled() {
+ for (Partition mPartition : mPartitions) {
+ if (mPartition.hasHeader) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns true for all items except headers.
+ */
+ @Override
+ public boolean isEnabled(int position) {
+ ensureCacheValid();
+ int start = 0;
+ for (int i = 0, n = mPartitions.size(); i < n; i++) {
+ int end = start + mPartitions.get(i).count;
+ if (position >= start && position < end) {
+ int offset = position - start;
+ if (mPartitions.get(i).hasHeader && offset == 0) {
+ return false;
+ } else {
+ return isEnabled(i, offset);
+ }
+ }
+ start = end;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns true if the item at the specified offset of the specified
+ * partition is selectable and clickable.
+ */
+ protected boolean isEnabled(int partition, int position) {
+ return true;
+ }
+
+ /**
+ * Enable or disable data change notifications. It may be a good idea to
+ * disable notifications before making changes to several partitions at once.
+ */
+ public void setNotificationsEnabled(boolean flag) {
+ mNotificationsEnabled = flag;
+ if (flag && mNotificationNeeded) {
+ notifyDataSetChanged();
+ }
+ }
+
+ @Override
+ public void notifyDataSetChanged() {
+ if (mNotificationsEnabled) {
+ mNotificationNeeded = false;
+ super.notifyDataSetChanged();
+ } else {
+ mNotificationNeeded = true;
+ }
+ }
+}
diff --git a/src/com/android/launcher3/list/PinnedHeaderListAdapter.java b/src/com/android/launcher3/list/PinnedHeaderListAdapter.java
new file mode 100644
index 000000000..c15e87a46
--- /dev/null
+++ b/src/com/android/launcher3/list/PinnedHeaderListAdapter.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.list;
+
+import android.content.Context;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A subclass of {@link CompositeCursorAdapter} that manages pinned partition headers.
+ */
+public abstract class PinnedHeaderListAdapter extends CompositeCursorAdapter
+ implements PinnedHeaderListView.PinnedHeaderAdapter {
+
+ public static final int PARTITION_HEADER_TYPE = 0;
+
+ private boolean mPinnedPartitionHeadersEnabled;
+ private boolean mHeaderVisibility[];
+
+ public PinnedHeaderListAdapter(Context context) {
+ super(context);
+ }
+
+ public PinnedHeaderListAdapter(Context context, int initialCapacity) {
+ super(context, initialCapacity);
+ }
+
+ public boolean getPinnedPartitionHeadersEnabled() {
+ return mPinnedPartitionHeadersEnabled;
+ }
+
+ public void setPinnedPartitionHeadersEnabled(boolean flag) {
+ this.mPinnedPartitionHeadersEnabled = flag;
+ }
+
+ @Override
+ public int getPinnedHeaderCount() {
+ if (mPinnedPartitionHeadersEnabled) {
+ return getPartitionCount();
+ } else {
+ return 0;
+ }
+ }
+
+ protected boolean isPinnedPartitionHeaderVisible(int partition) {
+ return getPinnedPartitionHeadersEnabled() && hasHeader(partition)
+ && !isPartitionEmpty(partition);
+ }
+
+ /**
+ * The default implementation creates the same type of view as a normal
+ * partition header.
+ */
+ @Override
+ public View getPinnedHeaderView(int partition, View convertView, ViewGroup parent) {
+ if (hasHeader(partition)) {
+ View view = null;
+ if (convertView != null) {
+ Integer headerType = (Integer)convertView.getTag();
+ if (headerType != null && headerType == PARTITION_HEADER_TYPE) {
+ view = convertView;
+ }
+ }
+ if (view == null) {
+ view = newHeaderView(getContext(), partition, null, parent);
+ view.setTag(PARTITION_HEADER_TYPE);
+ view.setFocusable(false);
+ view.setEnabled(false);
+ }
+ bindHeaderView(view, partition, getCursor(partition));
+ view.setLayoutDirection(parent.getLayoutDirection());
+ return view;
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public void configurePinnedHeaders(PinnedHeaderListView listView) {
+ if (!getPinnedPartitionHeadersEnabled()) {
+ return;
+ }
+
+ int size = getPartitionCount();
+ boolean unCached = false;
+ // Cache visibility bits, because we will need them several times later on
+ if (mHeaderVisibility == null || mHeaderVisibility.length != size) {
+ mHeaderVisibility = new boolean[size];
+ unCached = true;
+ }
+ for (int i = 0; i < size; i++) {
+ boolean visible = isPinnedPartitionHeaderVisible(i);
+ mHeaderVisibility[i] = visible;
+ if (!visible) {
+ listView.setHeaderInvisible(i, true);
+ }
+ }
+
+ int headerViewsCount = listView.getHeaderViewsCount();
+
+ // Starting at the top, find and pin headers for partitions preceding the visible one(s)
+ int maxTopHeader = -1;
+ int topHeaderHeight = 0;
+ for (int i = 0; i < size; i++) {
+ if (mHeaderVisibility[i]) {
+ int position = listView.getPositionAt(topHeaderHeight) - headerViewsCount;
+ int partition = getPartitionForPosition(position);
+ if (i > partition) {
+ break;
+ }
+
+ if (!unCached){
+ listView.setHeaderPinnedAtTop(i, topHeaderHeight, false);
+ topHeaderHeight += listView.getPinnedHeaderHeight(i);
+ maxTopHeader = i;
+ }
+
+ }
+ }
+
+ // Starting at the bottom, find and pin headers for partitions following the visible one(s)
+ /*int maxBottomHeader = size;
+ int bottomHeaderHeight = 0;
+ int listHeight = listView.getHeight();
+ for (int i = size; --i > maxTopHeader;) {
+ if (mHeaderVisibility[i]) {
+ int position = listView.getPositionAt(listHeight - bottomHeaderHeight)
+ - headerViewsCount;
+ if (position < 0) {
+ break;
+ }
+
+ int partition = getPartitionForPosition(position - 1);
+ if (partition == -1 || i <= partition) {
+ break;
+ }
+
+ int height = listView.getPinnedHeaderHeight(i);
+ bottomHeaderHeight += height;
+
+ listView.setHeaderPinnedAtBottom(i, listHeight - bottomHeaderHeight, false);
+ maxBottomHeader = i;
+ }
+ }
+
+ // Headers in between the top-pinned and bottom-pinned should be hidden
+ for (int i = maxTopHeader + 1; i < maxBottomHeader; i++) {
+ if (mHeaderVisibility[i]) {
+ listView.setHeaderInvisible(i, isPartitionEmpty(i));
+ }
+ }*/
+ }
+
+ @Override
+ public int getScrollPositionForHeader(int viewIndex) {
+ return getPositionForPartition(viewIndex);
+ }
+}
diff --git a/src/com/android/launcher3/list/PinnedHeaderListView.java b/src/com/android/launcher3/list/PinnedHeaderListView.java
new file mode 100644
index 000000000..30688fea3
--- /dev/null
+++ b/src/com/android/launcher3/list/PinnedHeaderListView.java
@@ -0,0 +1,565 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.list;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsListView;
+import android.widget.AbsListView.OnScrollListener;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.ListAdapter;
+
+/**
+ * A ListView that maintains a header pinned at the top of the list. The
+ * pinned header can be pushed up and dissolved as needed.
+ */
+public class PinnedHeaderListView extends AutoScrollListView
+ implements OnScrollListener, OnItemSelectedListener {
+
+ /**
+ * Adapter interface. The list adapter must implement this interface.
+ */
+ public interface PinnedHeaderAdapter {
+
+ /**
+ * Returns the overall number of pinned headers, visible or not.
+ */
+ int getPinnedHeaderCount();
+
+ /**
+ * Creates or updates the pinned header view.
+ */
+ View getPinnedHeaderView(int viewIndex, View convertView, ViewGroup parent);
+
+ /**
+ * Configures the pinned headers to match the visible list items. The
+ * adapter should call {@link PinnedHeaderListView#setHeaderPinnedAtTop},
+ * {@link PinnedHeaderListView#setHeaderPinnedAtBottom},
+ * {@link PinnedHeaderListView#setFadingHeader} or
+ * {@link PinnedHeaderListView#setHeaderInvisible}, for each header that
+ * needs to change its position or visibility.
+ */
+ void configurePinnedHeaders(PinnedHeaderListView listView);
+
+ /**
+ * Returns the list position to scroll to if the pinned header is touched.
+ * Return -1 if the list does not need to be scrolled.
+ */
+ int getScrollPositionForHeader(int viewIndex);
+ }
+
+ private static final int MAX_ALPHA = 255;
+ private static final int TOP = 0;
+ private static final int BOTTOM = 1;
+ private static final int FADING = 2;
+
+ private static final int DEFAULT_ANIMATION_DURATION = 20;
+
+ private static final int DEFAULT_SMOOTH_SCROLL_DURATION = 100;
+
+ private static final class PinnedHeader {
+ View view;
+ boolean visible;
+ int y;
+ int height;
+ int alpha;
+ int state;
+
+ boolean animating;
+ boolean targetVisible;
+ int sourceY;
+ int targetY;
+ long targetTime;
+ }
+
+ private PinnedHeaderAdapter mAdapter;
+ private int mSize;
+ private PinnedHeader[] mHeaders;
+ private RectF mBounds = new RectF();
+ private Rect mClipRect = new Rect();
+ private OnScrollListener mOnScrollListener;
+ private OnItemSelectedListener mOnItemSelectedListener;
+ private int mScrollState;
+
+ private boolean mScrollToSectionOnHeaderTouch = false;
+ private boolean mHeaderTouched = false;
+
+ private int mAnimationDuration = DEFAULT_ANIMATION_DURATION;
+ private boolean mAnimating;
+ private long mAnimationTargetTime;
+ private int mHeaderPaddingStart;
+ private int mHeaderWidth;
+
+ public PinnedHeaderListView(Context context) {
+ this(context, null, android.R.attr.listViewStyle);
+ }
+
+ public PinnedHeaderListView(Context context, AttributeSet attrs) {
+ this(context, attrs, android.R.attr.listViewStyle);
+ }
+
+ public PinnedHeaderListView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ super.setOnScrollListener(this);
+ super.setOnItemSelectedListener(this);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ super.onLayout(changed, l, t, r, b);
+ mHeaderPaddingStart = getPaddingStart();
+ mHeaderWidth = r - l - mHeaderPaddingStart - getPaddingEnd();
+ }
+
+ public void setPinnedHeaderAnimationDuration(int duration) {
+ mAnimationDuration = duration;
+ }
+
+ @Override
+ public void setAdapter(ListAdapter adapter) {
+ mAdapter = (PinnedHeaderAdapter)adapter;
+ super.setAdapter(adapter);
+ }
+
+ @Override
+ public void setOnScrollListener(OnScrollListener onScrollListener) {
+ mOnScrollListener = onScrollListener;
+ super.setOnScrollListener(this);
+ }
+
+ @Override
+ public void setOnItemSelectedListener(OnItemSelectedListener listener) {
+ mOnItemSelectedListener = listener;
+ super.setOnItemSelectedListener(this);
+ }
+
+ public void setScrollToSectionOnHeaderTouch(boolean value) {
+ mScrollToSectionOnHeaderTouch = value;
+ }
+
+ @Override
+ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
+ int totalItemCount) {
+ if (mAdapter != null) {
+ int count = mAdapter.getPinnedHeaderCount();
+ if (count != mSize) {
+ mSize = count;
+ if (mHeaders == null) {
+ mHeaders = new PinnedHeader[mSize];
+ } else if (mHeaders.length < mSize) {
+ PinnedHeader[] headers = mHeaders;
+ mHeaders = new PinnedHeader[mSize];
+ System.arraycopy(headers, 0, mHeaders, 0, headers.length);
+ }
+ }
+
+ for (int i = 0; i < mSize; i++) {
+ if (mHeaders[i] == null) {
+ mHeaders[i] = new PinnedHeader();
+ }
+ mHeaders[i].view = mAdapter.getPinnedHeaderView(i, mHeaders[i].view, this);
+ }
+
+ mAnimationTargetTime = System.currentTimeMillis() + mAnimationDuration;
+ mAdapter.configurePinnedHeaders(this);
+ invalidateIfAnimating();
+
+ }
+ if (mOnScrollListener != null) {
+ mOnScrollListener.onScroll(this, firstVisibleItem, visibleItemCount, totalItemCount);
+ }
+ }
+
+ @Override
+ protected float getTopFadingEdgeStrength() {
+ // Disable vertical fading at the top when the pinned header is present
+ return mSize > 0 ? 0 : super.getTopFadingEdgeStrength();
+ }
+
+ @Override
+ public void onScrollStateChanged(AbsListView view, int scrollState) {
+ mScrollState = scrollState;
+ if (mOnScrollListener != null) {
+ mOnScrollListener.onScrollStateChanged(this, scrollState);
+ }
+ }
+
+ /**
+ * Ensures that the selected item is positioned below the top-pinned headers
+ * and above the bottom-pinned ones.
+ */
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ int height = getHeight();
+
+ int windowTop = 0;
+ int windowBottom = height;
+
+ for (int i = 0; i < mSize; i++) {
+ PinnedHeader header = mHeaders[i];
+ if (header.visible) {
+ if (header.state == TOP) {
+ windowTop = header.y + header.height;
+ } else if (header.state == BOTTOM) {
+ windowBottom = header.y;
+ break;
+ }
+ }
+ }
+
+ View selectedView = getSelectedView();
+ if (selectedView != null) {
+ if (selectedView.getTop() < windowTop) {
+ setSelectionFromTop(position, windowTop);
+ } else if (selectedView.getBottom() > windowBottom) {
+ setSelectionFromTop(position, windowBottom - selectedView.getHeight());
+ }
+ }
+
+ if (mOnItemSelectedListener != null) {
+ mOnItemSelectedListener.onItemSelected(parent, view, position, id);
+ }
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ if (mOnItemSelectedListener != null) {
+ mOnItemSelectedListener.onNothingSelected(parent);
+ }
+ }
+
+ public int getPinnedHeaderHeight(int viewIndex) {
+ ensurePinnedHeaderLayout(viewIndex);
+ return mHeaders[viewIndex].view.getHeight();
+ }
+
+ /**
+ * Set header to be pinned at the top.
+ *
+ * @param viewIndex index of the header view
+ * @param y is position of the header in pixels.
+ * @param animate true if the transition to the new coordinate should be animated
+ */
+ public void setHeaderPinnedAtTop(int viewIndex, int y, boolean animate) {
+ ensurePinnedHeaderLayout(viewIndex);
+ PinnedHeader header = mHeaders[viewIndex];
+ header.visible = true;
+ header.y = y;
+ header.state = TOP;
+
+ // TODO perhaps we should animate at the top as well
+ header.animating = false;
+ }
+
+ /**
+ * Set header to be pinned at the bottom.
+ *
+ * @param viewIndex index of the header view
+ * @param y is position of the header in pixels.
+ * @param animate true if the transition to the new coordinate should be animated
+ */
+ public void setHeaderPinnedAtBottom(int viewIndex, int y, boolean animate) {
+ ensurePinnedHeaderLayout(viewIndex);
+ PinnedHeader header = mHeaders[viewIndex];
+ header.state = BOTTOM;
+ if (header.animating) {
+ header.targetTime = mAnimationTargetTime;
+ header.sourceY = header.y;
+ header.targetY = y;
+ } else if (animate && (header.y != y || !header.visible)) {
+ if (header.visible) {
+ header.sourceY = header.y;
+ } else {
+ header.visible = true;
+ header.sourceY = y + header.height;
+ }
+ header.animating = true;
+ header.targetVisible = true;
+ header.targetTime = mAnimationTargetTime;
+ header.targetY = y;
+ } else {
+ header.visible = true;
+ header.y = y;
+ }
+ }
+
+ /**
+ * Set header to be pinned at the top of the first visible item.
+ *
+ * @param viewIndex index of the header view
+ * @param position is position of the header in pixels.
+ */
+ public void setFadingHeader(int viewIndex, int position, boolean fade) {
+ ensurePinnedHeaderLayout(viewIndex);
+
+ View child = getChildAt(position - getFirstVisiblePosition());
+ if (child == null) return;
+
+ PinnedHeader header = mHeaders[viewIndex];
+ header.visible = true;
+ header.state = FADING;
+ header.alpha = MAX_ALPHA;
+ header.animating = false;
+
+ int top = getTotalTopPinnedHeaderHeight();
+ header.y = top;
+ if (fade) {
+ int bottom = child.getBottom() - top;
+ int headerHeight = header.height;
+ if (bottom < headerHeight) {
+ int portion = bottom - headerHeight;
+ header.alpha = MAX_ALPHA * (headerHeight + portion) / headerHeight;
+ header.y = top + portion;
+ }
+ }
+ }
+
+ /**
+ * Makes header invisible.
+ *
+ * @param viewIndex index of the header view
+ * @param animate true if the transition to the new coordinate should be animated
+ */
+ public void setHeaderInvisible(int viewIndex, boolean animate) {
+ PinnedHeader header = mHeaders[viewIndex];
+ if (header.visible && (animate || header.animating) && header.state == BOTTOM) {
+ header.sourceY = header.y;
+ if (!header.animating) {
+ header.visible = true;
+ header.targetY = getBottom() + header.height;
+ }
+ header.animating = true;
+ header.targetTime = mAnimationTargetTime;
+ header.targetVisible = false;
+ } else {
+ header.visible = false;
+ }
+ }
+
+ private void ensurePinnedHeaderLayout(int viewIndex) {
+ View view = mHeaders[viewIndex].view;
+ if (view.isLayoutRequested()) {
+ int widthSpec = View.MeasureSpec.makeMeasureSpec(mHeaderWidth, View.MeasureSpec.EXACTLY);
+ int heightSpec;
+ ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
+ if (layoutParams != null && layoutParams.height > 0) {
+ heightSpec = View.MeasureSpec
+ .makeMeasureSpec(layoutParams.height, View.MeasureSpec.EXACTLY);
+ } else {
+ heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+ }
+ view.measure(widthSpec, heightSpec);
+ int height = view.getMeasuredHeight();
+ mHeaders[viewIndex].height = height;
+ view.layout(0, 0, mHeaderWidth, height);
+ }
+ }
+
+ /**
+ * Returns the sum of heights of headers pinned to the top.
+ */
+ public int getTotalTopPinnedHeaderHeight() {
+ for (int i = mSize; --i >= 0;) {
+ PinnedHeader header = mHeaders[i];
+ if (header.visible && header.state == TOP) {
+ return header.y + header.height;
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * Returns the list item position at the specified y coordinate.
+ */
+ public int getPositionAt(int y) {
+ do {
+ int position = pointToPosition(getPaddingLeft() + 1, y);
+ if (position != -1) {
+ return position;
+ }
+ // If position == -1, we must have hit a separator. Let's examine
+ // a nearby pixel
+ y--;
+ } while (y > 0);
+ return 0;
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ mHeaderTouched = false;
+ if (super.onInterceptTouchEvent(ev)) {
+ return true;
+ }
+
+ if (mScrollState == SCROLL_STATE_IDLE) {
+ final int y = (int)ev.getY();
+ final int x = (int)ev.getX();
+ for (int i = mSize; --i >= 0;) {
+ PinnedHeader header = mHeaders[i];
+ // For RTL layouts, this also takes into account that the scrollbar is on the left
+ // side.
+ final int padding = getPaddingLeft();
+ if (header.visible && header.y <= y && header.y + header.height > y &&
+ x >= padding && padding + mHeaderWidth >= x) {
+ mHeaderTouched = true;
+ if (mScrollToSectionOnHeaderTouch &&
+ ev.getAction() == MotionEvent.ACTION_DOWN) {
+ return smoothScrollToPartition(i);
+ } else {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ if (mHeaderTouched) {
+ if (ev.getAction() == MotionEvent.ACTION_UP) {
+ mHeaderTouched = false;
+ }
+ return true;
+ }
+ return super.onTouchEvent(ev);
+ };
+
+ private boolean smoothScrollToPartition(int partition) {
+ final int position = mAdapter.getScrollPositionForHeader(partition);
+ if (position == -1) {
+ return false;
+ }
+
+ int offset = 0;
+ for (int i = 0; i < partition; i++) {
+ PinnedHeader header = mHeaders[i];
+ if (header.visible) {
+ offset += header.height;
+ }
+ }
+ smoothScrollToPositionFromTop(position + getHeaderViewsCount(), offset,
+ DEFAULT_SMOOTH_SCROLL_DURATION);
+ return true;
+ }
+
+ private void invalidateIfAnimating() {
+ mAnimating = false;
+ for (int i = 0; i < mSize; i++) {
+ if (mHeaders[i].animating) {
+ mAnimating = true;
+ invalidate();
+ return;
+ }
+ }
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ long currentTime = mAnimating ? System.currentTimeMillis() : 0;
+
+ int top = 0;
+ int bottom = getBottom();
+ boolean hasVisibleHeaders = false;
+ for (int i = 0; i < mSize; i++) {
+ PinnedHeader header = mHeaders[i];
+ if (header.visible) {
+ hasVisibleHeaders = true;
+ if (header.state == BOTTOM && header.y < bottom) {
+ bottom = header.y;
+ } else if (header.state == TOP || header.state == FADING) {
+ int newTop = header.y + header.height;
+ if (newTop > top) {
+ top = newTop;
+ }
+ }
+ }
+ }
+
+ if (hasVisibleHeaders) {
+ canvas.save();
+ mClipRect.set(0, top, getWidth(), bottom);
+ canvas.clipRect(mClipRect);
+ }
+
+ super.dispatchDraw(canvas);
+
+ if (hasVisibleHeaders) {
+ canvas.restore();
+
+ // First draw top headers, then the bottom ones to handle the Z axis correctly
+ for (int i = mSize; --i >= 0;) {
+ PinnedHeader header = mHeaders[i];
+ if (header.visible && (header.state == TOP || header.state == FADING)) {
+ drawHeader(canvas, header, currentTime);
+ }
+ }
+
+ for (int i = 0; i < mSize; i++) {
+ PinnedHeader header = mHeaders[i];
+ if (header.visible && header.state == BOTTOM) {
+ drawHeader(canvas, header, currentTime);
+ }
+ }
+ }
+
+ invalidateIfAnimating();
+ }
+
+ private void drawHeader(Canvas canvas, PinnedHeader header, long currentTime) {
+ if (header.animating) {
+ int timeLeft = (int)(header.targetTime - currentTime);
+ if (timeLeft <= 0) {
+ header.y = header.targetY;
+ header.visible = header.targetVisible;
+ header.animating = false;
+ } else {
+ header.y = header.targetY + (header.sourceY - header.targetY) * timeLeft
+ / mAnimationDuration;
+ }
+ }
+ if (header.visible) {
+ View view = header.view;
+ int saveCount = canvas.save();
+ canvas.translate(isLayoutRtl() ?
+ getWidth() - mHeaderPaddingStart - mHeaderWidth : mHeaderPaddingStart,
+ header.y);
+ if (header.state == FADING) {
+ mBounds.set(0, 0, mHeaderWidth, view.getHeight());
+ canvas.saveLayerAlpha(mBounds, header.alpha, Canvas.ALL_SAVE_FLAG);
+ }
+ view.draw(canvas);
+ canvas.restoreToCount(saveCount);
+ }
+ }
+
+ /**
+ * Note: this is a reimplementation of View.isLayoutRtl() since that is currently hidden api.
+ */
+ public boolean isLayoutRtl() {
+ return (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
+ }
+}
diff --git a/src/com/android/launcher3/list/SettingsPinnedHeaderAdapter.java b/src/com/android/launcher3/list/SettingsPinnedHeaderAdapter.java
new file mode 100644
index 000000000..805b51e37
--- /dev/null
+++ b/src/com/android/launcher3/list/SettingsPinnedHeaderAdapter.java
@@ -0,0 +1,373 @@
+package com.android.launcher3.list;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.Typeface;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+import android.widget.Toast;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.OverviewSettingsPanel;
+import com.android.launcher3.AppsCustomizePagedView;
+import com.android.launcher3.R;
+
+import com.android.launcher3.settings.SettingsProvider;
+import android.view.View.OnClickListener;
+import android.content.SharedPreferences;
+
+public class SettingsPinnedHeaderAdapter extends PinnedHeaderListAdapter {
+ private Launcher mLauncher;
+ private Context mContext;
+
+ public SettingsPinnedHeaderAdapter(Context context) {
+ super(context);
+ mLauncher = (Launcher) context;
+ mContext = context;
+ }
+
+ private String[] mHeaders;
+ public int mPinnedHeaderCount;
+
+ public void setHeaders(String[] headers) {
+ this.mHeaders = headers;
+ }
+
+ @Override
+ protected View newHeaderView(Context context, int partition, Cursor cursor,
+ ViewGroup parent) {
+ LayoutInflater inflater = LayoutInflater.from(context);
+ return inflater.inflate(R.layout.settings_pane_list_header, null);
+ }
+
+ @Override
+ protected void bindHeaderView(View view, int partition, Cursor cursor) {
+ TextView textView = (TextView) view.findViewById(R.id.item_name);
+ textView.setText(mHeaders[partition]);
+ textView.setTypeface(textView.getTypeface(), Typeface.BOLD);
+
+ textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16);
+ }
+
+ @Override
+ protected View newView(Context context, int partition, Cursor cursor, int position,
+ ViewGroup parent) {
+ LayoutInflater inflater = LayoutInflater.from(context);
+ return inflater.inflate(R.layout.settings_pane_list_item, null);
+ }
+
+ @Override
+ protected void bindView(View v, int partition, Cursor cursor, int position) {
+ TextView text = (TextView)v.findViewById(R.id.item_name);
+ String title = cursor.getString(1);
+ text.setText(title);
+
+ Resources res = mLauncher.getResources();
+
+ if (title.equals(res
+ .getString(R.string.home_screen_search_text))) {
+ boolean current = mLauncher.shouldShowSearchBar();
+ String state = current ? res.getString(
+ R.string.setting_state_on) : res.getString(
+ R.string.setting_state_off);
+ ((TextView) v.findViewById(R.id.item_state)).setText(state);
+ } else if (title.equals(res
+ .getString(R.string.drawer_sorting_text))) {
+ updateDrawerSortSettingsItem(v);
+ } else if (title.equals(res
+ .getString(R.string.scroll_effect_text)) &&
+ partition == OverviewSettingsPanel.DRAWER_SETTINGS_POSITION) {
+ String state = mLauncher.getAppsCustomizeTransitionEffect();
+ state = mapEffectToValue(state);
+ ((TextView) v.findViewById(R.id.item_state)).setText(state);
+ } else if (title.equals(res
+ .getString(R.string.scroll_effect_text)) &&
+ partition == OverviewSettingsPanel.HOME_SETTINGS_POSITION) {
+ String state = mLauncher.getWorkspaceTransitionEffect();
+ state = mapEffectToValue(state);
+ ((TextView) v.findViewById(R.id.item_state)).setText(state);
+ } else if (title.equals(res
+ .getString(R.string.larger_icons_text))) {
+ boolean current = SettingsProvider
+ .getBoolean(
+ mContext,
+ SettingsProvider.SETTINGS_UI_GENERAL_ICONS_LARGE,
+ R.bool.preferences_interface_general_icons_large_default);
+ String state = current ? res.getString(
+ R.string.setting_state_on) : res.getString(
+ R.string.setting_state_off);
+ ((TextView) v.findViewById(R.id.item_state)).setText(state);
+ } else if (title.equals(res
+ .getString(R.string.icon_labels)) &&
+ partition == OverviewSettingsPanel.HOME_SETTINGS_POSITION) {
+ boolean current = mLauncher.shouldHideWorkspaceIconLables();
+ String state = current ? res.getString(
+ R.string.icon_labels_hide) : res.getString(
+ R.string.icon_labels_show);
+ ((TextView) v.findViewById(R.id.item_state)).setText(state);
+ } else if (title.equals(res
+ .getString(R.string.icon_labels)) &&
+ partition == OverviewSettingsPanel.DRAWER_SETTINGS_POSITION) {
+ boolean current = SettingsProvider
+ .getBoolean(
+ mContext,
+ SettingsProvider.SETTINGS_UI_DRAWER_HIDE_ICON_LABELS,
+ R.bool.preferences_interface_drawer_hide_icon_labels_default);
+ String state = current ? res.getString(
+ R.string.icon_labels_hide) : res.getString(
+ R.string.icon_labels_show);
+ ((TextView) v.findViewById(R.id.item_state)).setText(state);
+ } else if (title.equals(res
+ .getString(R.string.search_screen_left_text))) {
+ boolean current = SettingsProvider
+ .getBoolean(
+ mContext,
+ SettingsProvider.SETTINGS_UI_HOMESCREEN_SEARCH_SCREEN_LEFT,
+ R.bool.preferences_interface_homescreen_search_screen_left_default);
+ String state = current ? res.getString(
+ R.string.setting_state_on) : res.getString(
+ R.string.setting_state_off);
+ ((TextView) v.findViewById(R.id.item_state)).setText(state);
+ } else if (title.equals(res.getString(R.string.scrolling_wallpaper))) {
+ boolean current = SettingsProvider
+ .getBoolean(
+ mContext,
+ SettingsProvider.SETTINGS_UI_HOMESCREEN_SCROLLING_WALLPAPER_SCROLL,
+ R.bool.preferences_interface_homescreen_scrolling_wallpaper_scroll_default);
+ String state = current ? res.getString(
+ R.string.setting_state_on) : res.getString(
+ R.string.setting_state_off);
+ ((TextView) v.findViewById(R.id.item_state)).setText(state);
+ } else if (title.equals(res.getString(R.string.grid_size_text))) {
+ updateDynamicGridSizeSettingsItem(v);
+ } else {
+ ((TextView) v.findViewById(R.id.item_state)).setText("");
+ }
+
+ v.setTag(partition);
+ v.setOnClickListener(mSettingsItemListener);
+ }
+
+ @Override
+ public View getPinnedHeaderView(int viewIndex, View convertView, ViewGroup parent) {
+ LayoutInflater inflater = LayoutInflater.from(getContext());
+ View view = inflater.inflate(R.layout.settings_pane_list_header, parent, false);
+ view.setFocusable(false);
+ view.setEnabled(false);
+ bindHeaderView(view, viewIndex, null);
+ return view;
+ }
+
+ @Override
+ public int getPinnedHeaderCount() {
+ return mPinnedHeaderCount;
+ }
+
+ public void updateDrawerSortSettingsItem(View v) {
+ String state = "";
+ switch (mLauncher.getAppsCustomizeContentSortMode()) {
+ case Title:
+ state = mLauncher.getResources().getString(R.string.sort_mode_title);
+ break;
+ case LaunchCount:
+ state = mLauncher.getResources().getString(
+ R.string.sort_mode_launch_count);
+ break;
+ case InstallTime:
+ state = mLauncher.getResources().getString(
+ R.string.sort_mode_install_time);
+ break;
+ }
+ ((TextView) v.findViewById(R.id.item_state)).setText(state);
+ }
+
+ public void updateDynamicGridSizeSettingsItem(View v) {
+ DeviceProfile.GridSize gridSize = DeviceProfile.GridSize.getModeForValue(
+ SettingsProvider.getIntCustomDefault(mLauncher,
+ SettingsProvider.SETTINGS_UI_DYNAMIC_GRID_SIZE, 0));
+ String state = "";
+
+ switch (gridSize) {
+ case Comfortable:
+ state = mLauncher.getResources().getString(R.string.grid_size_comfortable);
+ break;
+ case Cozy:
+ state = mLauncher.getResources().getString(R.string.grid_size_cozy);
+ break;
+ case Condensed:
+ state = mLauncher.getResources().getString(R.string.grid_size_condensed);
+ break;
+ case Custom:
+ int rows = SettingsProvider.getIntCustomDefault(mLauncher,
+ SettingsProvider.SETTINGS_UI_HOMESCREEN_ROWS, 0);
+ int columns = SettingsProvider.getIntCustomDefault(mLauncher,
+ SettingsProvider.SETTINGS_UI_HOMESCREEN_COLUMNS, 0);
+ state = rows + " " + "\u00d7" + " " + columns;
+ break;
+ }
+ ((TextView) v.findViewById(R.id.item_state)).setText(state);
+ }
+
+ private String mapEffectToValue(String effect) {
+ final String[] titles = mLauncher.getResources().getStringArray(
+ R.array.transition_effect_entries);
+ final String[] values = mLauncher.getResources().getStringArray(
+ R.array.transition_effect_values);
+
+ int length = values.length;
+ for (int i = 0; i < length; i++) {
+ if (effect.equals(values[i])) {
+ return titles[i];
+ }
+ }
+ return "";
+ }
+
+ OnClickListener mSettingsItemListener = new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ // TODO Auto-generated method stub
+ String value = ((TextView) v.findViewById(R.id.item_name)).getText().toString();
+ Resources res = mLauncher.getResources();
+
+ // Handle toggles or launch pickers
+ if (value.equals(res
+ .getString(R.string.home_screen_search_text))) {
+ onSettingsBooleanChanged(
+ v,
+ SettingsProvider.SETTINGS_UI_HOMESCREEN_SEARCH,
+ R.bool.preferences_interface_homescreen_search_default);
+ mLauncher.updateDynamicGrid();
+ } else if (value.equals(res
+ .getString(R.string.drawer_sorting_text))) {
+ onClickTransitionEffectButton();
+ } else if (value.equals(res
+ .getString(R.string.scroll_effect_text)) &&
+ ((Integer)v.getTag() == OverviewSettingsPanel.DRAWER_SETTINGS_POSITION)) {
+ mLauncher.onClickTransitionEffectButton(v, true);
+ } else if (value.equals(res
+ .getString(R.string.scroll_effect_text)) &&
+ ((Integer)v.getTag() == OverviewSettingsPanel.HOME_SETTINGS_POSITION)) {
+ mLauncher.onClickTransitionEffectButton(v, false);
+ } else if (value.equals(res
+ .getString(R.string.larger_icons_text))) {
+ onSettingsBooleanChanged(
+ v,
+ SettingsProvider.SETTINGS_UI_GENERAL_ICONS_LARGE,
+ R.bool.preferences_interface_general_icons_large_default);
+ mLauncher.updateDynamicGrid();
+ } else if (value.equals(res
+ .getString(R.string.icon_labels)) &&
+ ((Integer)v.getTag() == OverviewSettingsPanel.HOME_SETTINGS_POSITION)) {
+ onIconLabelsBooleanChanged(
+ v,
+ SettingsProvider.SETTINGS_UI_HOMESCREEN_HIDE_ICON_LABELS,
+ R.bool.preferences_interface_homescreen_hide_icon_labels_default);
+ mLauncher.updateDynamicGrid();
+ } else if (value.equals(res
+ .getString(R.string.icon_labels)) &&
+ ((Integer)v.getTag() == OverviewSettingsPanel.DRAWER_SETTINGS_POSITION)) {
+ onIconLabelsBooleanChanged(
+ v,
+ SettingsProvider.SETTINGS_UI_DRAWER_HIDE_ICON_LABELS,
+ R.bool.preferences_interface_drawer_hide_icon_labels_default);
+ mLauncher.updateDynamicGrid();
+ } else if (value.equals(res.getString(R.string.protected_app_settings))) {
+ Intent intent = new Intent();
+ intent.setClassName(OverviewSettingsPanel.ANDROID_SETTINGS,
+ OverviewSettingsPanel.ANDROID_PROTECTED_APPS);
+ mLauncher.startActivity(intent);
+ } else if (value.equals(res.getString(R.string.protected_app_settings))) {
+
+ } else if (value.equals(res
+ .getString(R.string.scrolling_wallpaper))) {
+ onSettingsBooleanChanged(
+ v,
+ SettingsProvider.SETTINGS_UI_HOMESCREEN_SCROLLING_WALLPAPER_SCROLL,
+ R.bool.preferences_interface_homescreen_scrolling_wallpaper_scroll_default);
+ mLauncher.updateDynamicGrid();
+ } else if (value.equals(res
+ .getString(R.string.search_screen_left_text)) &&
+ ((Integer)v.getTag() == OverviewSettingsPanel.HOME_SETTINGS_POSITION)) {
+
+ boolean current = SettingsProvider.getBoolean(mContext,
+ SettingsProvider.SETTINGS_UI_HOMESCREEN_SEARCH_SCREEN_LEFT,
+ R.bool.preferences_interface_homescreen_search_screen_left_default);
+
+ // If GEL integration is not supported, do not allow the user to turn it on.
+ if(!current && !mLauncher.isGelIntegrationSupported()) {
+ Toast.makeText(mLauncher.getApplicationContext(),
+ res.getString(R.string.search_screen_left_unsupported_toast),
+ Toast.LENGTH_SHORT).show();
+ } else {
+ onSettingsBooleanChanged(
+ v,
+ SettingsProvider.SETTINGS_UI_HOMESCREEN_SEARCH_SCREEN_LEFT,
+ R.bool.preferences_interface_homescreen_search_screen_left_default);
+ mLauncher.updateDynamicGrid();
+ }
+ } else if (value.equals(res
+ .getString(R.string.grid_size_text))) {
+ mLauncher.onClickDynamicGridSizeButton();
+ }
+
+ View defaultHome = mLauncher.findViewById(R.id.default_home_screen_panel);
+ defaultHome.setVisibility(getCursor(0).getCount() > 1 ? View.VISIBLE : View.GONE);
+ }
+ };
+
+ private void onSettingsBooleanChanged(View v, String key, int res) {
+ boolean current = SettingsProvider.getBoolean(
+ mContext, key, res);
+
+ // Set new state
+ SharedPreferences sharedPref = SettingsProvider
+ .get(mContext);
+ sharedPref.edit().putBoolean(key, !current).commit();
+ sharedPref.edit()
+ .putBoolean(SettingsProvider.SETTINGS_CHANGED, true)
+ .commit();
+
+ String state = current ? mLauncher.getResources().getString(
+ R.string.setting_state_off) : mLauncher.getResources().getString(
+ R.string.setting_state_on);
+ ((TextView) v.findViewById(R.id.item_state)).setText(state);
+ }
+
+ private void onIconLabelsBooleanChanged(View v, String key, int res) {
+ boolean current = SettingsProvider.getBoolean(
+ mContext, key, res);
+
+ // Set new state
+ SharedPreferences sharedPref = SettingsProvider
+ .get(mContext);
+ sharedPref.edit().putBoolean(key, !current).commit();
+ sharedPref.edit()
+ .putBoolean(SettingsProvider.SETTINGS_CHANGED, true)
+ .commit();
+
+ String state = current ? mLauncher.getResources().getString(
+ R.string.icon_labels_show) : mLauncher.getResources().getString(
+ R.string.icon_labels_hide);
+ ((TextView) v.findViewById(R.id.item_state)).setText(state);
+ }
+
+ private void onClickTransitionEffectButton() {
+ int sort = SettingsProvider.getIntCustomDefault(mLauncher,
+ SettingsProvider.SETTINGS_UI_DRAWER_SORT_MODE, 0);
+
+ sort = (sort + 1) % AppsCustomizePagedView.SortMode.values().length;
+ mLauncher.getAppsCustomizeContent().setSortMode(
+ AppsCustomizePagedView.SortMode.getModeForValue(sort));
+
+ SettingsProvider.putInt(mLauncher, SettingsProvider.SETTINGS_UI_DRAWER_SORT_MODE, sort);
+
+ notifyDataSetChanged();
+ }
+}
diff --git a/src/com/android/launcher3/settings/FontStylePreference.java b/src/com/android/launcher3/settings/FontStylePreference.java
deleted file mode 100644
index 50df4b280..000000000
--- a/src/com/android/launcher3/settings/FontStylePreference.java
+++ /dev/null
@@ -1,142 +0,0 @@
-package com.android.launcher3.settings;
-
-import android.app.AlertDialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.graphics.Typeface;
-import android.preference.ListPreference;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.*;
-import com.android.launcher3.R;
-
-import java.util.Arrays;
-import java.util.List;
-
-public class FontStylePreference extends ListPreference {
- private String mValue;
- public int mClickedDialogEntryIndex;
- private boolean mValueSet;
-
- public FontStylePreference(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
- if (getEntries() == null || getEntryValues() == null) {
- throw new IllegalStateException(
- "ListPreference requires an entries array and an entryValues array.");
- }
-
- mClickedDialogEntryIndex = getValueIndex();
- builder.setSingleChoiceItems(new FontStyleAdapter(getContext()), mClickedDialogEntryIndex,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- mClickedDialogEntryIndex = which;
-
- /*
- * Clicking on an item simulates the positive button
- * click, and dismisses the dialog.
- */
- FontStylePreference.this.onClick(dialog, DialogInterface.BUTTON_POSITIVE);
- dialog.dismiss();
- }
- });
-
- /*
- * The typical interaction for list-based dialogs is to have
- * click-on-an-item dismiss the dialog instead of the user having to
- * press 'Ok'.
- */
- builder.setPositiveButton(null, null);
- }
-
- @Override
- protected void onDialogClosed(boolean positiveResult) {
- super.onDialogClosed(positiveResult);
-
- if (positiveResult && mClickedDialogEntryIndex >= 0 && getEntryValues() != null) {
- String value = getEntryValues()[mClickedDialogEntryIndex].toString();
- if (callChangeListener(value)) {
- setValue(value);
- }
- }
- }
-
- @Override
- protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
- setValue(restoreValue ? getPersistedString(mValue) : (String) defaultValue);
- }
-
- /**
- * Sets the value of the key. This should be one of the entries in
- * {@link #getEntryValues()}.
- *
- * @param value The value to set for the key.
- */
- @Override
- public void setValue(String value) {
- // Always persist/notify the first time.
- final boolean changed = !TextUtils.equals(mValue, value);
- if (changed || !mValueSet) {
- mValue = value;
- mValueSet = true;
- persistString(value);
- if (changed) {
- notifyChanged();
- }
- }
- }
-
- /**
- * Returns the value of the key. This should be one of the entries in
- * {@link #getEntryValues()}.
- *
- * @return The value of the key.
- */
- @Override
- public String getValue() {
- return mValue;
- }
-
- private int getValueIndex() {
- return findIndexOfValue(mValue);
- }
-
- private class FontStyleAdapter extends ArrayAdapter<CharSequence> {
- private LayoutInflater mInflater;
- private List<CharSequence> mEntries;
- private List<CharSequence> mValues;
-
- public FontStyleAdapter(Context context) {
- super(context, -1, getEntryValues());
-
- mInflater = LayoutInflater.from(context);
- mEntries = Arrays.asList(getEntries());
- mValues = Arrays.asList(getEntryValues());
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- CheckedTextView textView;
-
- if (convertView == null) {
- textView = (CheckedTextView) mInflater.inflate(R.layout.list_item_checkable, parent, false);
- } else {
- textView = (CheckedTextView) convertView;
- }
-
- if (textView != null) {
- textView.setText(mEntries.get(position));
- textView.setTag(mValues.get(position));
- textView.setTypeface(Typeface.create((String) mValues.get(position), Typeface.NORMAL));
- }
-
- return textView;
- }
- }
-}
diff --git a/src/com/android/launcher3/settings/HiddenAppsActivity.java b/src/com/android/launcher3/settings/HiddenAppsActivity.java
deleted file mode 100644
index 57ea7bc36..000000000
--- a/src/com/android/launcher3/settings/HiddenAppsActivity.java
+++ /dev/null
@@ -1,302 +0,0 @@
-package com.android.launcher3.settings;
-
-import android.app.ListActivity;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.graphics.drawable.Drawable;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.util.Log;
-import android.util.SparseBooleanArray;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.Window;
-import android.widget.ArrayAdapter;
-import android.widget.ImageView;
-import android.widget.ListView;
-import android.widget.TextView;
-
-import com.android.launcher3.R;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.ConcurrentHashMap;
-
-public class HiddenAppsActivity extends ListActivity {
-
- private boolean mSaved;
-
- private static final int MENU_RESET = 0;
-
- private PackageManager mPackageManager;
-
- private AppsAdapter mAppsAdapter;
-
- protected void onCreate(Bundle savedInstanceState) {
- requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
- super.onCreate(savedInstanceState);
-
- setTitle(R.string.hidden_apps_title);
- setContentView(R.layout.hidden_apps_list);
-
- getActionBar().setDisplayHomeAsUpEnabled(true);
- setProgressBarIndeterminateVisibility(true);
- setProgressBarIndeterminate(true);
-
- mPackageManager = getPackageManager();
- mAppsAdapter = new AppsAdapter(this, R.layout.hidden_apps_list_item);
- mAppsAdapter.setNotifyOnChange(true);
-
- setListAdapter(mAppsAdapter);
-
- AsyncTask<Void, Void, List<AppEntry>> refreshAppsTask = new AsyncTask<Void, Void, List<AppEntry>>() {
-
- @Override
- protected void onPostExecute(List<AppEntry> apps) {
- mAppsAdapter.clear();
- mAppsAdapter.addAll(apps);
- restoreCheckedItems();
- setProgressBarIndeterminateVisibility(false);
- setProgressBarIndeterminate(false);
- }
-
- @Override
- protected List<AppEntry> doInBackground(Void... params) {
- return refreshApps();
- }
- };
- refreshAppsTask.execute(null, null, null);
- }
-
- @Override
- public void onPause() {
- super.onPause();
- save();
- }
-
- private void save() {
- if (mSaved) {
- return;
- }
- String string = "";
-
- SparseBooleanArray checked = getListView().getCheckedItemPositions();
-
- AppsAdapter listAdapter = (AppsAdapter) getListAdapter();
- for (int i = 0; i < checked.size(); i++) {
- if (checked.valueAt(i)) {
- AppEntry app = listAdapter.getItem(checked.keyAt(i));
- if (!string.isEmpty())
- string += "|";
- string += app.componentName.flattenToString();
- }
- }
-
- SharedPreferences.Editor editor = SettingsProvider.get(this).edit();
- editor.putString(SettingsProvider.SETTINGS_UI_DRAWER_HIDDEN_APPS, string);
- editor.putBoolean(SettingsProvider.SETTINGS_CHANGED, true);
- editor.apply();
-
- mSaved = true;
- }
-
- private void restoreCheckedItems() {
- List<ComponentName> apps = new ArrayList<ComponentName>();
- String[] flattened = SettingsProvider.getStringCustomDefault(this,
- SettingsProvider.SETTINGS_UI_DRAWER_HIDDEN_APPS, "").split("\\|");
- for (String flat : flattened) {
- apps.add(ComponentName.unflattenFromString(flat));
- }
-
- AppsAdapter listAdapter = (AppsAdapter) getListAdapter();
-
- for (int i = 0; i < listAdapter.getCount(); i++) {
- AppEntry info = listAdapter.getItem(i);
- if (apps.contains(info.componentName)) {
- getListView().setItemChecked(i, true);
- }
- }
-
- mSaved = true;
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- menu.add(0, MENU_RESET, 0, R.string.menu_hidden_apps_delete)
- .setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
-
- return true;
- }
-
- private void reset() {
- for (int i = 0; i < getListView().getCount(); i++) {
- getListView().setItemChecked(i, false);
- }
-
- mSaved = false;
- }
-
- private List<AppEntry> refreshApps() {
- Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
- mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
- List<ResolveInfo> apps = mPackageManager.queryIntentActivities(mainIntent, 0);
- Collections.sort(apps, new ResolveInfo.DisplayNameComparator(mPackageManager));
- List<AppEntry> appEntries = new ArrayList<AppEntry>(apps.size());
- for (ResolveInfo info : apps) {
- appEntries.add(new AppEntry(info));
- }
- return appEntries;
- }
-
- @Override
- public boolean onMenuItemSelected(int featureId, MenuItem item) {
- if (item.getItemId() == android.R.id.home) {
- finish();
- return true;
- } else if (item.getItemId() == MENU_RESET) {
- reset();
- return true;
- }
- return super.onMenuItemSelected(featureId, item);
- }
-
- @Override
- protected void onListItemClick(ListView l, View v, int position, long id) {
- mSaved = false;
- }
-
- private final class AppEntry {
-
- public final ComponentName componentName;
- public final String title;
-
- public AppEntry(ResolveInfo info) {
- componentName = new ComponentName(info.activityInfo.packageName, info.activityInfo.name);
- title = info.loadLabel(mPackageManager).toString();
- }
- }
-
- /**
- * App view holder used to reuse the views inside the list.
- */
- private static class AppViewHolder {
- public final TextView title;
- public final ImageView icon;
-
- public AppViewHolder(View parentView) {
- icon = (ImageView) parentView.findViewById(R.id.icon);
- title = (TextView) parentView.findViewById(R.id.title);
- }
- }
-
- public class AppsAdapter extends ArrayAdapter<AppEntry> {
-
- private final LayoutInflater mInflator;
-
- private ConcurrentHashMap<String, Drawable> mIcons;
- private Drawable mDefaultImg;
- private List<AppEntry> mApps;
-
- public AppsAdapter(Context context, int textViewResourceId) {
- super(context, textViewResourceId);
-
- mApps = new ArrayList<AppEntry>();
-
- mInflator = LayoutInflater.from(context);
-
- // set the default icon till the actual app icon is loaded in async
- // task
- mDefaultImg = context.getResources().getDrawable(android.R.mipmap.sym_def_app_icon);
- mIcons = new ConcurrentHashMap<String, Drawable>();
- }
-
- @Override
- public View getView(final int position, View convertView, ViewGroup parent) {
- AppViewHolder viewHolder;
-
- if (convertView == null) {
- convertView = mInflator.inflate(R.layout.hidden_apps_list_item, parent, false);
- viewHolder = new AppViewHolder(convertView);
- convertView.setTag(viewHolder);
- } else {
- viewHolder = (AppViewHolder) convertView.getTag();
- }
-
- AppEntry app = getItem(position);
-
- viewHolder.title.setText(app.title);
-
- Drawable icon = mIcons.get(app.componentName.getPackageName());
- viewHolder.icon.setImageDrawable(icon != null ? icon : mDefaultImg);
-
- return convertView;
- }
-
- @Override
- public boolean hasStableIds() {
- return true;
- }
-
- @Override
- public void notifyDataSetChanged() {
- super.notifyDataSetChanged();
- // If we have new items, we have to load their icons
- // If items were deleted, remove them from our mApps
- List<AppEntry> newApps = new ArrayList<AppEntry>(getCount());
- List<AppEntry> oldApps = new ArrayList<AppEntry>(getCount());
- for (int i = 0; i < getCount(); i++) {
- AppEntry app = getItem(i);
- if (mApps.contains(app)) {
- oldApps.add(app);
- } else {
- newApps.add(app);
- }
- }
-
- if (newApps.size() > 0) {
- new LoadIconsTask().execute(newApps.toArray(new AppEntry[] {}));
- newApps.addAll(oldApps);
- mApps = newApps;
- } else {
- mApps = oldApps;
- }
- }
-
- /**
- * An asynchronous task to load the icons of the installed applications.
- */
- private class LoadIconsTask extends AsyncTask<AppEntry, Void, Void> {
- @Override
- protected Void doInBackground(AppEntry... apps) {
- for (AppEntry app : apps) {
- try {
- if (mIcons.containsKey(app.componentName.getPackageName())) {
- continue;
- }
- Drawable icon = mPackageManager.getApplicationIcon(app.componentName
- .getPackageName());
- mIcons.put(app.componentName.getPackageName(), icon);
- publishProgress();
- } catch (PackageManager.NameNotFoundException e) {
- // ignored; app will show up with default image
- }
- }
-
- return null;
- }
-
- @Override
- protected void onProgressUpdate(Void... progress) {
- notifyDataSetChanged();
- }
- }
- }
-}
diff --git a/src/com/android/launcher3/settings/SettingsActivity.java b/src/com/android/launcher3/settings/SettingsActivity.java
deleted file mode 100644
index dbbf95f52..000000000
--- a/src/com/android/launcher3/settings/SettingsActivity.java
+++ /dev/null
@@ -1,274 +0,0 @@
-/*
- * Copyright (C) 2013 The CyanogenMod 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.settings;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.os.Bundle;
-import android.preference.*;
-import android.text.TextUtils;
-import android.view.MenuItem;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
-import android.widget.ImageView;
-import android.widget.ListAdapter;
-import android.widget.TextView;
-import com.android.launcher3.R;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class SettingsActivity extends PreferenceActivity
- implements SharedPreferences.OnSharedPreferenceChangeListener {
- private static final String TAG = "Launcher3.SettingsActivity";
-
- private SharedPreferences mSettings;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- mSettings = getSharedPreferences(SettingsProvider.SETTINGS_KEY,
- Context.MODE_PRIVATE);
-
- getActionBar().setDisplayHomeAsUpEnabled(true);
- }
-
- @Override
- public SharedPreferences getSharedPreferences(String name, int mode) {
- return super.getSharedPreferences(SettingsProvider.SETTINGS_KEY,
- Context.MODE_PRIVATE);
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- mSettings.registerOnSharedPreferenceChangeListener(this);
- }
-
- @Override
- protected void onPause() {
- super.onPause();
- mSettings.unregisterOnSharedPreferenceChangeListener(this);
- }
-
- @Override
- public boolean isValidFragment(String fragmentName) {
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- onBackPressed();
- finish();
- return true;
- default:
- break;
- }
- return super.onOptionsItemSelected(item);
- }
-
- @Override
- public void onBuildHeaders(List<Header> target) {
- loadHeadersFromResource(R.xml.preferences_headers, target);
- updateHeaders(target);
- }
-
- private void updateHeaders(List<Header> headers) {
- int i = 0;
- while (i < headers.size()) {
- Header header = headers.get(i);
-
- // Version preference
- if (header.id == R.id.preferences_application_version) {
- header.title = getString(R.string.cm_application_name) + " " + getString(R.string.application_version);
- }
-
- // Increment if not removed
- if (headers.get(i) == header) {
- i++;
- }
- }
- }
-
- @Override
- public void setListAdapter(ListAdapter adapter) {
- if (adapter == null) {
- super.setListAdapter(null);
- } else {
- List<Header> headers = getHeadersFromAdapter(adapter);
- super.setListAdapter(new HeaderAdapter(this, headers));
- }
- }
-
- @Override
- public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
- SharedPreferences.Editor editor = mSettings.edit();
- editor.putBoolean(SettingsProvider.SETTINGS_CHANGED, true);
- editor.commit();
- }
-
- private List<Header> getHeadersFromAdapter(ListAdapter adapter) {
- List<Header> headers = new ArrayList<Header>();
- int count = adapter.getCount();
- for (int i = 0; i < count; i++) {
- headers.add((Header)adapter.getItem(i));
- }
- return headers;
- }
-
- public static class HomescreenFragment extends PreferenceFragment {
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- addPreferencesFromResource(R.xml.preferences_homescreen);
- }
- }
-
- public static class GeneralFragment extends PreferenceFragment {
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- addPreferencesFromResource(R.xml.preferences_general);
- }
- }
-
- public static class DrawerFragment extends PreferenceFragment {
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- addPreferencesFromResource(R.xml.preferences_drawer);
- }
- }
-
- private static class HeaderAdapter extends ArrayAdapter<Header> {
- private static final int HEADER_TYPE_NORMAL = 0;
- private static final int HEADER_TYPE_CATEGORY = 1;
-
- private static final int HEADER_TYPE_COUNT = HEADER_TYPE_CATEGORY + 1;
-
- private static class HeaderViewHolder {
- ImageView icon;
- TextView title;
- TextView summary;
- }
-
- private LayoutInflater mInflater;
-
- static int getHeaderType(Header header) {
- if (header.id == R.id.preferences_application_section) {
- return HEADER_TYPE_CATEGORY;
- } else {
- return HEADER_TYPE_NORMAL;
- }
- }
-
- @Override
- public int getItemViewType(int position) {
- Header header = getItem(position);
- return getHeaderType(header);
- }
-
- @Override
- public boolean areAllItemsEnabled() {
- return false; // because of categories
- }
-
- @Override
- public boolean isEnabled(int position) {
- return getItemViewType(position) != HEADER_TYPE_CATEGORY;
- }
-
- @Override
- public int getViewTypeCount() {
- return HEADER_TYPE_COUNT;
- }
-
- @Override
- public boolean hasStableIds() {
- return true;
- }
-
- public HeaderAdapter(Context context, List<Header> objects) {
- super(context, 0, objects);
-
- mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- HeaderViewHolder holder;
- Header header = getItem(position);
- int headerType = getHeaderType(header);
- View view = null;
-
- if (convertView == null) {
- holder = new HeaderViewHolder();
- switch (headerType) {
- case HEADER_TYPE_CATEGORY:
- view = new TextView(getContext(), null,
- android.R.attr.listSeparatorTextViewStyle);
- holder.title = (TextView) view;
- break;
-
- case HEADER_TYPE_NORMAL:
- view = mInflater.inflate(
- R.layout.preference_header_item, parent,
- false);
- holder.icon = (ImageView) view.findViewById(android.R.id.icon);
- holder.title = (TextView)
- view.findViewById(android.R.id.title);
- holder.summary = (TextView)
- view.findViewById(android.R.id.summary);
- break;
- }
- view.setTag(holder);
- } else {
- view = convertView;
- holder = (HeaderViewHolder) view.getTag();
- }
-
- // All view fields must be updated every time, because the view may be recycled
- switch (headerType) {
- case HEADER_TYPE_CATEGORY:
- holder.title.setText(header.getTitle(getContext().getResources()));
- break;
-
- case HEADER_TYPE_NORMAL:
- holder.icon.setImageResource(header.iconRes);
- holder.title.setText(header.getTitle(getContext().getResources()));
- CharSequence summary = header.getSummary(getContext().getResources());
- if (!TextUtils.isEmpty(summary)) {
- holder.summary.setVisibility(View.VISIBLE);
- holder.summary.setText(summary);
- } else {
- holder.summary.setVisibility(View.GONE);
- }
- break;
- }
-
- return view;
- }
- }
-}
diff --git a/src/com/android/launcher3/settings/SettingsProvider.java b/src/com/android/launcher3/settings/SettingsProvider.java
index abf4e1b67..32329ab83 100644
--- a/src/com/android/launcher3/settings/SettingsProvider.java
+++ b/src/com/android/launcher3/settings/SettingsProvider.java
@@ -26,21 +26,24 @@ public final class SettingsProvider {
public static final String SETTINGS_UI_HOMESCREEN_DEFAULT_SCREEN_ID = "ui_homescreen_default_screen_id";
public static final String SETTINGS_UI_HOMESCREEN_SEARCH = "ui_homescreen_search";
+ public static final String SETTINGS_UI_HOMESCREEN_SEARCH_SCREEN_LEFT = "ui_homescreen_search_screen_left";
public static final String SETTINGS_UI_HOMESCREEN_HIDE_ICON_LABELS = "ui_homescreen_general_hide_icon_labels";
public static final String SETTINGS_UI_HOMESCREEN_SCROLLING_TRANSITION_EFFECT = "ui_homescreen_scrolling_transition_effect";
public static final String SETTINGS_UI_HOMESCREEN_SCROLLING_WALLPAPER_SCROLL = "ui_homescreen_scrolling_wallpaper_scroll";
public static final String SETTINGS_UI_HOMESCREEN_SCROLLING_PAGE_OUTLINES = "ui_homescreen_scrolling_page_outlines";
public static final String SETTINGS_UI_HOMESCREEN_SCROLLING_FADE_ADJACENT = "ui_homescreen_scrolling_fade_adjacent";
+ public static final String SETTINGS_UI_DYNAMIC_GRID_SIZE = "ui_dynamic_grid_size";
+ public static final String SETTINGS_UI_HOMESCREEN_ROWS = "ui_homescreen_rows";
+ public static final String SETTINGS_UI_HOMESCREEN_COLUMNS = "ui_homescreen_columns";
public static final String SETTINGS_UI_DRAWER_SCROLLING_TRANSITION_EFFECT = "ui_drawer_scrolling_transition_effect";
public static final String SETTINGS_UI_DRAWER_SCROLLING_FADE_ADJACENT = "ui_drawer_scrolling_fade_adjacent";
- public static final String SETTINGS_UI_DRAWER_HIDDEN_APPS = "ui_drawer_hidden_apps";
public static final String SETTINGS_UI_DRAWER_REMOVE_HIDDEN_APPS_SHORTCUTS = "ui_drawer_remove_hidden_apps_shortcuts";
public static final String SETTINGS_UI_DRAWER_REMOVE_HIDDEN_APPS_WIDGETS = "ui_drawer_remove_hidden_apps_widgets";
public static final String SETTINGS_UI_DRAWER_HIDE_ICON_LABELS = "ui_drawer_hide_icon_labels";
public static final String SETTINGS_UI_GENERAL_ICONS_LARGE = "ui_general_icons_large";
- public static final String SETTINGS_UI_GENERAL_ICONS_TEXT_FONT_FAMILY = "ui_general_icons_text_font";
- public static final String SETTINGS_UI_GENERAL_ICONS_TEXT_FONT_STYLE = "ui_general_icons_text_font_style";
- public static final String SETTINGS_UI_GENERAL_ICONS_ICON_PACK = "ui_general_iconpack";
+ public static final String SETTINGS_UI_DRAWER_SORT_MODE = "ui_drawer_sort_mode";
+
+ public static final String SETTINGS_HOME_LAST_APP = "home_last_app";
public static SharedPreferences get(Context context) {
return context.getSharedPreferences(SETTINGS_KEY, Context.MODE_MULTI_PROCESS);
@@ -81,4 +84,8 @@ public final class SettingsProvider {
public static void putString(Context context, String key, String value) {
get(context).edit().putString(key, value).commit();
}
+
+ public static void putInt(Context context, String key, int value) {
+ get(context).edit().putInt(key, value).commit();
+ }
}
diff --git a/src/com/android/photos/BitmapRegionTileSource.java b/src/com/android/photos/BitmapRegionTileSource.java
deleted file mode 100644
index 4d1a84ed7..000000000
--- a/src/com/android/photos/BitmapRegionTileSource.java
+++ /dev/null
@@ -1,288 +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.photos;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.content.res.AssetManager;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-import android.graphics.BitmapFactory;
-import android.graphics.BitmapRegionDecoder;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Build.VERSION_CODES;
-import android.util.Log;
-
-import com.android.gallery3d.common.BitmapUtils;
-import com.android.gallery3d.glrenderer.BasicTexture;
-import com.android.gallery3d.glrenderer.BitmapTexture;
-import com.android.photos.views.TiledImageRenderer;
-
-import java.io.BufferedInputStream;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * A {@link com.android.photos.views.TiledImageRenderer.TileSource} using
- * {@link BitmapRegionDecoder} to wrap a local file
- */
-@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
-public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
-
- private static final String TAG = "BitmapRegionTileSource";
-
- private static final boolean REUSE_BITMAP =
- Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
- private static final int GL_SIZE_LIMIT = 2048;
- // This must be no larger than half the size of the GL_SIZE_LIMIT
- // due to decodePreview being allowed to be up to 2x the size of the target
- private static final int MAX_PREVIEW_SIZE = 1024;
-
- BitmapRegionDecoder mDecoder;
- int mWidth;
- int mHeight;
- int mTileSize;
- private BasicTexture mPreview;
- private final int mRotation;
-
- // For use only by getTile
- private Rect mWantRegion = new Rect();
- private Rect mOverlapRegion = new Rect();
- private BitmapFactory.Options mOptions;
- private Canvas mCanvas;
-
- public BitmapRegionTileSource(Context context, String path, int previewSize, int rotation) {
- this(null, context, path, null, 0, previewSize, rotation, false);
- }
-
- public BitmapRegionTileSource(Resources res, Context context, String path, int previewSize, int rotation, boolean assetPath) {
- this(res, context, path, null, 0, previewSize, rotation, assetPath);
- }
-
- public BitmapRegionTileSource(Context context, Uri uri, int previewSize, int rotation) {
- this(null, context, null, uri, 0, previewSize, rotation, false);
- }
-
- public BitmapRegionTileSource(Resources res,
- Context context, int resId, int previewSize, int rotation) {
- this(res, context, null, null, resId, previewSize, rotation, false);
- }
-
- private BitmapRegionTileSource(Resources res,
- Context context, String path, Uri uri, int resId, int previewSize, int rotation, boolean assetPath) {
- mTileSize = TiledImageRenderer.suggestedTileSize(context);
- mRotation = rotation;
- try {
- if (path != null && !assetPath) {
- mDecoder = BitmapRegionDecoder.newInstance(path, true);
- } else if (path != null && res != null && assetPath) {
- AssetManager am = res.getAssets();
- String[] pathImages = am.list(path);
- if (pathImages == null || pathImages.length == 0) {
- throw new IOException("did not find any images in path: " + path);
- }
- InputStream is = am.open(path + File.separator + pathImages[0]);
- BufferedInputStream bis = new BufferedInputStream(is);
- mDecoder = BitmapRegionDecoder.newInstance(bis, true);
- } else if (uri != null) {
- InputStream is = context.getContentResolver().openInputStream(uri);
- BufferedInputStream bis = new BufferedInputStream(is);
- mDecoder = BitmapRegionDecoder.newInstance(bis, true);
- } else {
- InputStream is = res.openRawResource(resId);
- BufferedInputStream bis = new BufferedInputStream(is);
- mDecoder = BitmapRegionDecoder.newInstance(bis, true);
- }
- mWidth = mDecoder.getWidth();
- mHeight = mDecoder.getHeight();
- } catch (IOException e) {
- Log.w("BitmapRegionTileSource", "ctor failed", e);
- }
- mOptions = new BitmapFactory.Options();
- mOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;
- mOptions.inPreferQualityOverSpeed = true;
- mOptions.inTempStorage = new byte[16 * 1024];
- if (previewSize != 0) {
- previewSize = Math.min(previewSize, MAX_PREVIEW_SIZE);
- // Although this is the same size as the Bitmap that is likely already
- // loaded, the lifecycle is different and interactions are on a different
- // thread. Thus to simplify, this source will decode its own bitmap.
- Bitmap preview = decodePreview(res, context, path, uri, resId, previewSize, assetPath);
- if (preview.getWidth() <= GL_SIZE_LIMIT && preview.getHeight() <= GL_SIZE_LIMIT) {
- mPreview = new BitmapTexture(preview);
- } else {
- Log.w(TAG, String.format(
- "Failed to create preview of apropriate size! "
- + " in: %dx%d, out: %dx%d",
- mWidth, mHeight,
- preview.getWidth(), preview.getHeight()));
- }
- }
- }
-
- @Override
- public int getTileSize() {
- return mTileSize;
- }
-
- @Override
- public int getImageWidth() {
- return mWidth;
- }
-
- @Override
- public int getImageHeight() {
- return mHeight;
- }
-
- @Override
- public BasicTexture getPreview() {
- return mPreview;
- }
-
- @Override
- public int getRotation() {
- return mRotation;
- }
-
- @Override
- public Bitmap getTile(int level, int x, int y, Bitmap bitmap) {
- int tileSize = getTileSize();
- if (!REUSE_BITMAP) {
- return getTileWithoutReusingBitmap(level, x, y, tileSize);
- }
-
- int t = tileSize << level;
- mWantRegion.set(x, y, x + t, y + t);
-
- if (bitmap == null) {
- bitmap = Bitmap.createBitmap(tileSize, tileSize, Bitmap.Config.ARGB_8888);
- }
-
- mOptions.inSampleSize = (1 << level);
- mOptions.inBitmap = bitmap;
-
- try {
- bitmap = mDecoder.decodeRegion(mWantRegion, mOptions);
- } finally {
- if (mOptions.inBitmap != bitmap && mOptions.inBitmap != null) {
- mOptions.inBitmap = null;
- }
- }
-
- if (bitmap == null) {
- Log.w("BitmapRegionTileSource", "fail in decoding region");
- }
- return bitmap;
- }
-
- private Bitmap getTileWithoutReusingBitmap(
- int level, int x, int y, int tileSize) {
-
- int t = tileSize << level;
- mWantRegion.set(x, y, x + t, y + t);
-
- mOverlapRegion.set(0, 0, mWidth, mHeight);
-
- mOptions.inSampleSize = (1 << level);
- Bitmap bitmap = mDecoder.decodeRegion(mOverlapRegion, mOptions);
-
- if (bitmap == null) {
- Log.w(TAG, "fail in decoding region");
- }
-
- if (mWantRegion.equals(mOverlapRegion)) {
- return bitmap;
- }
-
- Bitmap result = Bitmap.createBitmap(tileSize, tileSize, Config.ARGB_8888);
- if (mCanvas == null) {
- mCanvas = new Canvas();
- }
- mCanvas.setBitmap(result);
- mCanvas.drawBitmap(bitmap,
- (mOverlapRegion.left - mWantRegion.left) >> level,
- (mOverlapRegion.top - mWantRegion.top) >> level, null);
- mCanvas.setBitmap(null);
- return result;
- }
-
- /**
- * Note that the returned bitmap may have a long edge that's longer
- * than the targetSize, but it will always be less than 2x the targetSize
- */
- private Bitmap decodePreview(
- Resources res, Context context, String file, Uri uri, int resId, int targetSize, boolean assetPath) {
- float scale = (float) targetSize / Math.max(mWidth, mHeight);
- mOptions.inSampleSize = BitmapUtils.computeSampleSizeLarger(scale);
- mOptions.inJustDecodeBounds = false;
-
- Bitmap result = null;
- if (file != null && res != null && assetPath) {
- try {
- AssetManager am = res.getAssets();
- String[] pathImages = am.list(file);
- if (pathImages == null || pathImages.length == 0) {
- throw new IOException("did not find any images in path: " + file);
- }
- InputStream is = am.open(file + File.separator + pathImages[0]);
- BufferedInputStream bis = new BufferedInputStream(is);
- result = BitmapFactory.decodeStream(bis, null, mOptions);
- } catch (IOException e) {
- Log.w("BitmapRegionTileSource", "getting preview failed", e);
- }
- } else if (file != null) {
- result = BitmapFactory.decodeFile(file, mOptions);
- } else if (uri != null) {
- try {
- InputStream is = context.getContentResolver().openInputStream(uri);
- BufferedInputStream bis = new BufferedInputStream(is);
- result = BitmapFactory.decodeStream(bis, null, mOptions);
- } catch (IOException e) {
- Log.w("BitmapRegionTileSource", "getting preview failed", e);
- }
- } else {
- result = BitmapFactory.decodeResource(res, resId, mOptions);
- }
- if (result == null) {
- return null;
- }
-
- // We need to resize down if the decoder does not support inSampleSize
- // or didn't support the specified inSampleSize (some decoders only do powers of 2)
- scale = (float) targetSize / (float) (Math.max(result.getWidth(), result.getHeight()));
-
- if (scale <= 0.5) {
- result = BitmapUtils.resizeBitmapByScale(result, scale, true);
- }
- return ensureGLCompatibleBitmap(result);
- }
-
- private static Bitmap ensureGLCompatibleBitmap(Bitmap bitmap) {
- if (bitmap == null || bitmap.getConfig() != null) {
- return bitmap;
- }
- Bitmap newBitmap = bitmap.copy(Config.ARGB_8888, false);
- bitmap.recycle();
- return newBitmap;
- }
-}
diff --git a/src/com/android/photos/views/BlockingGLTextureView.java b/src/com/android/photos/views/BlockingGLTextureView.java
deleted file mode 100644
index 8a0505185..000000000
--- a/src/com/android/photos/views/BlockingGLTextureView.java
+++ /dev/null
@@ -1,438 +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.photos.views;
-
-import android.content.Context;
-import android.graphics.SurfaceTexture;
-import android.opengl.GLSurfaceView.Renderer;
-import android.opengl.GLUtils;
-import android.util.Log;
-import android.view.TextureView;
-import android.view.TextureView.SurfaceTextureListener;
-
-import javax.microedition.khronos.egl.EGL10;
-import javax.microedition.khronos.egl.EGLConfig;
-import javax.microedition.khronos.egl.EGLContext;
-import javax.microedition.khronos.egl.EGLDisplay;
-import javax.microedition.khronos.egl.EGLSurface;
-import javax.microedition.khronos.opengles.GL10;
-
-/**
- * A TextureView that supports blocking rendering for synchronous drawing
- */
-public class BlockingGLTextureView extends TextureView
- implements SurfaceTextureListener {
-
- private RenderThread mRenderThread;
-
- public BlockingGLTextureView(Context context) {
- super(context);
- setSurfaceTextureListener(this);
- }
-
- public void setRenderer(Renderer renderer) {
- if (mRenderThread != null) {
- throw new IllegalArgumentException("Renderer already set");
- }
- mRenderThread = new RenderThread(renderer);
- }
-
- public void render() {
- mRenderThread.render();
- }
-
- public void destroy() {
- if (mRenderThread != null) {
- mRenderThread.finish();
- mRenderThread = null;
- }
- }
-
- @Override
- public void onSurfaceTextureAvailable(SurfaceTexture surface, int width,
- int height) {
- mRenderThread.setSurface(surface);
- mRenderThread.setSize(width, height);
- }
-
- @Override
- public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width,
- int height) {
- mRenderThread.setSize(width, height);
- }
-
- @Override
- public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
- if (mRenderThread != null) {
- mRenderThread.setSurface(null);
- }
- return false;
- }
-
- @Override
- public void onSurfaceTextureUpdated(SurfaceTexture surface) {
- }
-
- @Override
- protected void finalize() throws Throwable {
- try {
- destroy();
- } catch (Throwable t) {
- // Ignore
- }
- super.finalize();
- }
-
- /**
- * An EGL helper class.
- */
-
- private static class EglHelper {
- private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
- private static final int EGL_OPENGL_ES2_BIT = 4;
-
- EGL10 mEgl;
- EGLDisplay mEglDisplay;
- EGLSurface mEglSurface;
- EGLConfig mEglConfig;
- EGLContext mEglContext;
-
- private EGLConfig chooseEglConfig() {
- int[] configsCount = new int[1];
- EGLConfig[] configs = new EGLConfig[1];
- int[] configSpec = getConfig();
- if (!mEgl.eglChooseConfig(mEglDisplay, configSpec, configs, 1, configsCount)) {
- throw new IllegalArgumentException("eglChooseConfig failed " +
- GLUtils.getEGLErrorString(mEgl.eglGetError()));
- } else if (configsCount[0] > 0) {
- return configs[0];
- }
- return null;
- }
-
- private static int[] getConfig() {
- return new int[] {
- EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
- EGL10.EGL_RED_SIZE, 8,
- EGL10.EGL_GREEN_SIZE, 8,
- EGL10.EGL_BLUE_SIZE, 8,
- EGL10.EGL_ALPHA_SIZE, 8,
- EGL10.EGL_DEPTH_SIZE, 0,
- EGL10.EGL_STENCIL_SIZE, 0,
- EGL10.EGL_NONE
- };
- }
-
- EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) {
- int[] attribList = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE };
- return egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, attribList);
- }
-
- /**
- * Initialize EGL for a given configuration spec.
- */
- public void start() {
- /*
- * Get an EGL instance
- */
- mEgl = (EGL10) EGLContext.getEGL();
-
- /*
- * Get to the default display.
- */
- mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
-
- if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
- throw new RuntimeException("eglGetDisplay failed");
- }
-
- /*
- * We can now initialize EGL for that display
- */
- int[] version = new int[2];
- if (!mEgl.eglInitialize(mEglDisplay, version)) {
- throw new RuntimeException("eglInitialize failed");
- }
- mEglConfig = chooseEglConfig();
-
- /*
- * Create an EGL context. We want to do this as rarely as we can, because an
- * EGL context is a somewhat heavy object.
- */
- mEglContext = createContext(mEgl, mEglDisplay, mEglConfig);
-
- if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) {
- mEglContext = null;
- throwEglException("createContext");
- }
-
- mEglSurface = null;
- }
-
- /**
- * Create an egl surface for the current SurfaceTexture surface. If a surface
- * already exists, destroy it before creating the new surface.
- *
- * @return true if the surface was created successfully.
- */
- public boolean createSurface(SurfaceTexture surface) {
- /*
- * Check preconditions.
- */
- if (mEgl == null) {
- throw new RuntimeException("egl not initialized");
- }
- if (mEglDisplay == null) {
- throw new RuntimeException("eglDisplay not initialized");
- }
- if (mEglConfig == null) {
- throw new RuntimeException("mEglConfig not initialized");
- }
-
- /*
- * The window size has changed, so we need to create a new
- * surface.
- */
- destroySurfaceImp();
-
- /*
- * Create an EGL surface we can render into.
- */
- if (surface != null) {
- mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, surface, null);
- } else {
- mEglSurface = null;
- }
-
- if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
- int error = mEgl.eglGetError();
- if (error == EGL10.EGL_BAD_NATIVE_WINDOW) {
- Log.e("EglHelper", "createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
- }
- return false;
- }
-
- /*
- * Before we can issue GL commands, we need to make sure
- * the context is current and bound to a surface.
- */
- if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
- /*
- * Could not make the context current, probably because the underlying
- * SurfaceView surface has been destroyed.
- */
- logEglErrorAsWarning("EGLHelper", "eglMakeCurrent", mEgl.eglGetError());
- return false;
- }
-
- return true;
- }
-
- /**
- * Create a GL object for the current EGL context.
- */
- public GL10 createGL() {
- return (GL10) mEglContext.getGL();
- }
-
- /**
- * Display the current render surface.
- * @return the EGL error code from eglSwapBuffers.
- */
- public int swap() {
- if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
- return mEgl.eglGetError();
- }
- return EGL10.EGL_SUCCESS;
- }
-
- public void destroySurface() {
- destroySurfaceImp();
- }
-
- private void destroySurfaceImp() {
- if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) {
- mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE,
- EGL10.EGL_NO_SURFACE,
- EGL10.EGL_NO_CONTEXT);
- mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
- mEglSurface = null;
- }
- }
-
- public void finish() {
- if (mEglContext != null) {
- mEgl.eglDestroyContext(mEglDisplay, mEglContext);
- mEglContext = null;
- }
- if (mEglDisplay != null) {
- mEgl.eglTerminate(mEglDisplay);
- mEglDisplay = null;
- }
- }
-
- private void throwEglException(String function) {
- throwEglException(function, mEgl.eglGetError());
- }
-
- public static void throwEglException(String function, int error) {
- String message = formatEglError(function, error);
- throw new RuntimeException(message);
- }
-
- public static void logEglErrorAsWarning(String tag, String function, int error) {
- Log.w(tag, formatEglError(function, error));
- }
-
- public static String formatEglError(String function, int error) {
- return function + " failed: " + error;
- }
-
- }
-
- private static class RenderThread extends Thread {
- private static final int INVALID = -1;
- private static final int RENDER = 1;
- private static final int CHANGE_SURFACE = 2;
- private static final int RESIZE_SURFACE = 3;
- private static final int FINISH = 4;
-
- private EglHelper mEglHelper = new EglHelper();
-
- private Object mLock = new Object();
- private int mExecMsgId = INVALID;
- private SurfaceTexture mSurface;
- private Renderer mRenderer;
- private int mWidth, mHeight;
-
- private boolean mFinished = false;
- private GL10 mGL;
-
- public RenderThread(Renderer renderer) {
- super("RenderThread");
- mRenderer = renderer;
- start();
- }
-
- private void checkRenderer() {
- if (mRenderer == null) {
- throw new IllegalArgumentException("Renderer is null!");
- }
- }
-
- private void checkSurface() {
- if (mSurface == null) {
- throw new IllegalArgumentException("surface is null!");
- }
- }
-
- public void setSurface(SurfaceTexture surface) {
- // If the surface is null we're being torn down, don't need a
- // renderer then
- if (surface != null) {
- checkRenderer();
- }
- mSurface = surface;
- exec(CHANGE_SURFACE);
- }
-
- public void setSize(int width, int height) {
- checkRenderer();
- checkSurface();
- mWidth = width;
- mHeight = height;
- exec(RESIZE_SURFACE);
- }
-
- public void render() {
- checkRenderer();
- if (mSurface != null) {
- exec(RENDER);
- mSurface.updateTexImage();
- }
- }
-
- public void finish() {
- mSurface = null;
- exec(FINISH);
- try {
- join();
- } catch (InterruptedException e) {
- // Ignore
- }
- }
-
- private void exec(int msgid) {
- synchronized (mLock) {
- if (mExecMsgId != INVALID) {
- throw new IllegalArgumentException(
- "Message already set - multithreaded access?");
- }
- mExecMsgId = msgid;
- mLock.notify();
- try {
- mLock.wait();
- } catch (InterruptedException e) {
- // Ignore
- }
- }
- }
-
- private void handleMessageLocked(int what) {
- switch (what) {
- case CHANGE_SURFACE:
- if (mEglHelper.createSurface(mSurface)) {
- mGL = mEglHelper.createGL();
- mRenderer.onSurfaceCreated(mGL, mEglHelper.mEglConfig);
- }
- break;
- case RESIZE_SURFACE:
- mRenderer.onSurfaceChanged(mGL, mWidth, mHeight);
- break;
- case RENDER:
- mRenderer.onDrawFrame(mGL);
- mEglHelper.swap();
- break;
- case FINISH:
- mEglHelper.destroySurface();
- mEglHelper.finish();
- mFinished = true;
- break;
- }
- }
-
- @Override
- public void run() {
- synchronized (mLock) {
- mEglHelper.start();
- while (!mFinished) {
- while (mExecMsgId == INVALID) {
- try {
- mLock.wait();
- } catch (InterruptedException e) {
- // Ignore
- }
- }
- handleMessageLocked(mExecMsgId);
- mExecMsgId = INVALID;
- mLock.notify();
- }
- mExecMsgId = FINISH;
- }
- }
- }
-}
diff --git a/src/com/android/photos/views/TiledImageRenderer.java b/src/com/android/photos/views/TiledImageRenderer.java
deleted file mode 100644
index c4e493b34..000000000
--- a/src/com/android/photos/views/TiledImageRenderer.java
+++ /dev/null
@@ -1,825 +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.photos.views;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.support.v4.util.LongSparseArray;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.util.Pools.Pool;
-import android.util.Pools.SynchronizedPool;
-import android.view.View;
-import android.view.WindowManager;
-
-import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.glrenderer.BasicTexture;
-import com.android.gallery3d.glrenderer.GLCanvas;
-import com.android.gallery3d.glrenderer.UploadedTexture;
-
-/**
- * Handles laying out, decoding, and drawing of tiles in GL
- */
-public class TiledImageRenderer {
- public static final int SIZE_UNKNOWN = -1;
-
- private static final String TAG = "TiledImageRenderer";
- private static final int UPLOAD_LIMIT = 1;
-
- /*
- * This is the tile state in the CPU side.
- * Life of a Tile:
- * ACTIVATED (initial state)
- * --> IN_QUEUE - by queueForDecode()
- * --> RECYCLED - by recycleTile()
- * IN_QUEUE --> DECODING - by decodeTile()
- * --> RECYCLED - by recycleTile)
- * DECODING --> RECYCLING - by recycleTile()
- * --> DECODED - by decodeTile()
- * --> DECODE_FAIL - by decodeTile()
- * RECYCLING --> RECYCLED - by decodeTile()
- * DECODED --> ACTIVATED - (after the decoded bitmap is uploaded)
- * DECODED --> RECYCLED - by recycleTile()
- * DECODE_FAIL -> RECYCLED - by recycleTile()
- * RECYCLED --> ACTIVATED - by obtainTile()
- */
- private static final int STATE_ACTIVATED = 0x01;
- private static final int STATE_IN_QUEUE = 0x02;
- private static final int STATE_DECODING = 0x04;
- private static final int STATE_DECODED = 0x08;
- private static final int STATE_DECODE_FAIL = 0x10;
- private static final int STATE_RECYCLING = 0x20;
- private static final int STATE_RECYCLED = 0x40;
-
- private static Pool<Bitmap> sTilePool = new SynchronizedPool<Bitmap>(64);
-
- // TILE_SIZE must be 2^N
- private int mTileSize;
-
- private TileSource mModel;
- private BasicTexture mPreview;
- protected int mLevelCount; // cache the value of mScaledBitmaps.length
-
- // The mLevel variable indicates which level of bitmap we should use.
- // Level 0 means the original full-sized bitmap, and a larger value means
- // a smaller scaled bitmap (The width and height of each scaled bitmap is
- // half size of the previous one). If the value is in [0, mLevelCount), we
- // use the bitmap in mScaledBitmaps[mLevel] for display, otherwise the value
- // is mLevelCount
- private int mLevel = 0;
-
- private int mOffsetX;
- private int mOffsetY;
-
- private int mUploadQuota;
- private boolean mRenderComplete;
-
- private final RectF mSourceRect = new RectF();
- private final RectF mTargetRect = new RectF();
-
- private final LongSparseArray<Tile> mActiveTiles = new LongSparseArray<Tile>();
-
- // The following three queue are guarded by mQueueLock
- private final Object mQueueLock = new Object();
- private final TileQueue mRecycledQueue = new TileQueue();
- private final TileQueue mUploadQueue = new TileQueue();
- private final TileQueue mDecodeQueue = new TileQueue();
-
- // The width and height of the full-sized bitmap
- protected int mImageWidth = SIZE_UNKNOWN;
- protected int mImageHeight = SIZE_UNKNOWN;
-
- protected int mCenterX;
- protected int mCenterY;
- protected float mScale;
- protected int mRotation;
-
- private boolean mLayoutTiles;
-
- // Temp variables to avoid memory allocation
- private final Rect mTileRange = new Rect();
- private final Rect mActiveRange[] = {new Rect(), new Rect()};
-
- private TileDecoder mTileDecoder;
- private boolean mBackgroundTileUploaded;
-
- private int mViewWidth, mViewHeight;
- private View mParent;
-
- /**
- * Interface for providing tiles to a {@link TiledImageRenderer}
- */
- public static interface TileSource {
-
- /**
- * If the source does not care about the tile size, it should use
- * {@link TiledImageRenderer#suggestedTileSize(Context)}
- */
- public int getTileSize();
- public int getImageWidth();
- public int getImageHeight();
- public int getRotation();
-
- /**
- * Return a Preview image if available. This will be used as the base layer
- * if higher res tiles are not yet available
- */
- public BasicTexture getPreview();
-
- /**
- * The tile returned by this method can be specified this way: Assuming
- * the image size is (width, height), first take the intersection of (0,
- * 0) - (width, height) and (x, y) - (x + tileSize, y + tileSize). If
- * in extending the region, we found some part of the region is outside
- * the image, those pixels are filled with black.
- *
- * If level > 0, it does the same operation on a down-scaled version of
- * the original image (down-scaled by a factor of 2^level), but (x, y)
- * still refers to the coordinate on the original image.
- *
- * The method would be called by the decoder thread.
- */
- public Bitmap getTile(int level, int x, int y, Bitmap reuse);
- }
-
- public static int suggestedTileSize(Context context) {
- return isHighResolution(context) ? 512 : 256;
- }
-
- private static boolean isHighResolution(Context context) {
- DisplayMetrics metrics = new DisplayMetrics();
- WindowManager wm = (WindowManager)
- context.getSystemService(Context.WINDOW_SERVICE);
- wm.getDefaultDisplay().getMetrics(metrics);
- return metrics.heightPixels > 2048 || metrics.widthPixels > 2048;
- }
-
- public TiledImageRenderer(View parent) {
- mParent = parent;
- mTileDecoder = new TileDecoder();
- mTileDecoder.start();
- }
-
- public int getViewWidth() {
- return mViewWidth;
- }
-
- public int getViewHeight() {
- return mViewHeight;
- }
-
- private void invalidate() {
- mParent.postInvalidate();
- }
-
- public void setModel(TileSource model, int rotation) {
- if (mModel != model) {
- mModel = model;
- notifyModelInvalidated();
- }
- if (mRotation != rotation) {
- mRotation = rotation;
- mLayoutTiles = true;
- }
- }
-
- private void calculateLevelCount() {
- if (mPreview != null) {
- mLevelCount = Math.max(0, Utils.ceilLog2(
- mImageWidth / (float) mPreview.getWidth()));
- } else {
- int levels = 1;
- int maxDim = Math.max(mImageWidth, mImageHeight);
- int t = mTileSize;
- while (t < maxDim) {
- t <<= 1;
- levels++;
- }
- mLevelCount = levels;
- }
- }
-
- public void notifyModelInvalidated() {
- invalidateTiles();
- if (mModel == null) {
- mImageWidth = 0;
- mImageHeight = 0;
- mLevelCount = 0;
- mPreview = null;
- } else {
- mImageWidth = mModel.getImageWidth();
- mImageHeight = mModel.getImageHeight();
- mPreview = mModel.getPreview();
- mTileSize = mModel.getTileSize();
- calculateLevelCount();
- }
- mLayoutTiles = true;
- }
-
- public void setViewSize(int width, int height) {
- mViewWidth = width;
- mViewHeight = height;
- }
-
- public void setPosition(int centerX, int centerY, float scale) {
- if (mCenterX == centerX && mCenterY == centerY
- && mScale == scale) {
- return;
- }
- mCenterX = centerX;
- mCenterY = centerY;
- mScale = scale;
- mLayoutTiles = true;
- }
-
- // Prepare the tiles we want to use for display.
- //
- // 1. Decide the tile level we want to use for display.
- // 2. Decide the tile levels we want to keep as texture (in addition to
- // the one we use for display).
- // 3. Recycle unused tiles.
- // 4. Activate the tiles we want.
- private void layoutTiles() {
- if (mViewWidth == 0 || mViewHeight == 0 || !mLayoutTiles) {
- return;
- }
- mLayoutTiles = false;
-
- // The tile levels we want to keep as texture is in the range
- // [fromLevel, endLevel).
- int fromLevel;
- int endLevel;
-
- // We want to use a texture larger than or equal to the display size.
- mLevel = Utils.clamp(Utils.floorLog2(1f / mScale), 0, mLevelCount);
-
- // We want to keep one more tile level as texture in addition to what
- // we use for display. So it can be faster when the scale moves to the
- // next level. We choose the level closest to the current scale.
- if (mLevel != mLevelCount) {
- Rect range = mTileRange;
- getRange(range, mCenterX, mCenterY, mLevel, mScale, mRotation);
- mOffsetX = Math.round(mViewWidth / 2f + (range.left - mCenterX) * mScale);
- mOffsetY = Math.round(mViewHeight / 2f + (range.top - mCenterY) * mScale);
- fromLevel = mScale * (1 << mLevel) > 0.75f ? mLevel - 1 : mLevel;
- } else {
- // Activate the tiles of the smallest two levels.
- fromLevel = mLevel - 2;
- mOffsetX = Math.round(mViewWidth / 2f - mCenterX * mScale);
- mOffsetY = Math.round(mViewHeight / 2f - mCenterY * mScale);
- }
-
- fromLevel = Math.max(0, Math.min(fromLevel, mLevelCount - 2));
- endLevel = Math.min(fromLevel + 2, mLevelCount);
-
- Rect range[] = mActiveRange;
- for (int i = fromLevel; i < endLevel; ++i) {
- getRange(range[i - fromLevel], mCenterX, mCenterY, i, mRotation);
- }
-
- // If rotation is transient, don't update the tile.
- if (mRotation % 90 != 0) {
- return;
- }
-
- synchronized (mQueueLock) {
- mDecodeQueue.clean();
- mUploadQueue.clean();
- mBackgroundTileUploaded = false;
-
- // Recycle unused tiles: if the level of the active tile is outside the
- // range [fromLevel, endLevel) or not in the visible range.
- int n = mActiveTiles.size();
- for (int i = 0; i < n; i++) {
- Tile tile = mActiveTiles.valueAt(i);
- int level = tile.mTileLevel;
- if (level < fromLevel || level >= endLevel
- || !range[level - fromLevel].contains(tile.mX, tile.mY)) {
- mActiveTiles.removeAt(i);
- i--;
- n--;
- recycleTile(tile);
- }
- }
- }
-
- for (int i = fromLevel; i < endLevel; ++i) {
- int size = mTileSize << i;
- Rect r = range[i - fromLevel];
- for (int y = r.top, bottom = r.bottom; y < bottom; y += size) {
- for (int x = r.left, right = r.right; x < right; x += size) {
- activateTile(x, y, i);
- }
- }
- }
- invalidate();
- }
-
- private void invalidateTiles() {
- synchronized (mQueueLock) {
- mDecodeQueue.clean();
- mUploadQueue.clean();
-
- // TODO(xx): disable decoder
- int n = mActiveTiles.size();
- for (int i = 0; i < n; i++) {
- Tile tile = mActiveTiles.valueAt(i);
- recycleTile(tile);
- }
- mActiveTiles.clear();
- }
- }
-
- private void getRange(Rect out, int cX, int cY, int level, int rotation) {
- getRange(out, cX, cY, level, 1f / (1 << (level + 1)), rotation);
- }
-
- // If the bitmap is scaled by the given factor "scale", return the
- // rectangle containing visible range. The left-top coordinate returned is
- // aligned to the tile boundary.
- //
- // (cX, cY) is the point on the original bitmap which will be put in the
- // center of the ImageViewer.
- private void getRange(Rect out,
- int cX, int cY, int level, float scale, int rotation) {
-
- double radians = Math.toRadians(-rotation);
- double w = mViewWidth;
- double h = mViewHeight;
-
- double cos = Math.cos(radians);
- double sin = Math.sin(radians);
- int width = (int) Math.ceil(Math.max(
- Math.abs(cos * w - sin * h), Math.abs(cos * w + sin * h)));
- int height = (int) Math.ceil(Math.max(
- Math.abs(sin * w + cos * h), Math.abs(sin * w - cos * h)));
-
- int left = (int) Math.floor(cX - width / (2f * scale));
- int top = (int) Math.floor(cY - height / (2f * scale));
- int right = (int) Math.ceil(left + width / scale);
- int bottom = (int) Math.ceil(top + height / scale);
-
- // align the rectangle to tile boundary
- int size = mTileSize << level;
- left = Math.max(0, size * (left / size));
- top = Math.max(0, size * (top / size));
- right = Math.min(mImageWidth, right);
- bottom = Math.min(mImageHeight, bottom);
-
- out.set(left, top, right, bottom);
- }
-
- public void freeTextures() {
- mLayoutTiles = true;
-
- mTileDecoder.finishAndWait();
- synchronized (mQueueLock) {
- mUploadQueue.clean();
- mDecodeQueue.clean();
- Tile tile = mRecycledQueue.pop();
- while (tile != null) {
- tile.recycle();
- tile = mRecycledQueue.pop();
- }
- }
-
- int n = mActiveTiles.size();
- for (int i = 0; i < n; i++) {
- Tile texture = mActiveTiles.valueAt(i);
- texture.recycle();
- }
- mActiveTiles.clear();
- mTileRange.set(0, 0, 0, 0);
-
- while (sTilePool.acquire() != null) {}
- }
-
- public boolean draw(GLCanvas canvas) {
- layoutTiles();
- uploadTiles(canvas);
-
- mUploadQuota = UPLOAD_LIMIT;
- mRenderComplete = true;
-
- int level = mLevel;
- int rotation = mRotation;
- int flags = 0;
- if (rotation != 0) {
- flags |= GLCanvas.SAVE_FLAG_MATRIX;
- }
-
- if (flags != 0) {
- canvas.save(flags);
- if (rotation != 0) {
- int centerX = mViewWidth / 2, centerY = mViewHeight / 2;
- canvas.translate(centerX, centerY);
- canvas.rotate(rotation, 0, 0, 1);
- canvas.translate(-centerX, -centerY);
- }
- }
- try {
- if (level != mLevelCount) {
- int size = (mTileSize << level);
- float length = size * mScale;
- Rect r = mTileRange;
-
- for (int ty = r.top, i = 0; ty < r.bottom; ty += size, i++) {
- float y = mOffsetY + i * length;
- for (int tx = r.left, j = 0; tx < r.right; tx += size, j++) {
- float x = mOffsetX + j * length;
- drawTile(canvas, tx, ty, level, x, y, length);
- }
- }
- } else if (mPreview != null) {
- mPreview.draw(canvas, mOffsetX, mOffsetY,
- Math.round(mImageWidth * mScale),
- Math.round(mImageHeight * mScale));
- }
- } finally {
- if (flags != 0) {
- canvas.restore();
- }
- }
-
- if (mRenderComplete) {
- if (!mBackgroundTileUploaded) {
- uploadBackgroundTiles(canvas);
- }
- } else {
- invalidate();
- }
- return mRenderComplete || mPreview != null;
- }
-
- private void uploadBackgroundTiles(GLCanvas canvas) {
- mBackgroundTileUploaded = true;
- int n = mActiveTiles.size();
- for (int i = 0; i < n; i++) {
- Tile tile = mActiveTiles.valueAt(i);
- if (!tile.isContentValid()) {
- queueForDecode(tile);
- }
- }
- }
-
- private void queueForDecode(Tile tile) {
- synchronized (mQueueLock) {
- if (tile.mTileState == STATE_ACTIVATED) {
- tile.mTileState = STATE_IN_QUEUE;
- if (mDecodeQueue.push(tile)) {
- mQueueLock.notifyAll();
- }
- }
- }
- }
-
- private void decodeTile(Tile tile) {
- synchronized (mQueueLock) {
- if (tile.mTileState != STATE_IN_QUEUE) {
- return;
- }
- tile.mTileState = STATE_DECODING;
- }
- boolean decodeComplete = tile.decode();
- synchronized (mQueueLock) {
- if (tile.mTileState == STATE_RECYCLING) {
- tile.mTileState = STATE_RECYCLED;
- if (tile.mDecodedTile != null) {
- sTilePool.release(tile.mDecodedTile);
- tile.mDecodedTile = null;
- }
- mRecycledQueue.push(tile);
- return;
- }
- tile.mTileState = decodeComplete ? STATE_DECODED : STATE_DECODE_FAIL;
- if (!decodeComplete) {
- return;
- }
- mUploadQueue.push(tile);
- }
- invalidate();
- }
-
- private Tile obtainTile(int x, int y, int level) {
- synchronized (mQueueLock) {
- Tile tile = mRecycledQueue.pop();
- if (tile != null) {
- tile.mTileState = STATE_ACTIVATED;
- tile.update(x, y, level);
- return tile;
- }
- return new Tile(x, y, level);
- }
- }
-
- private void recycleTile(Tile tile) {
- synchronized (mQueueLock) {
- if (tile.mTileState == STATE_DECODING) {
- tile.mTileState = STATE_RECYCLING;
- return;
- }
- tile.mTileState = STATE_RECYCLED;
- if (tile.mDecodedTile != null) {
- sTilePool.release(tile.mDecodedTile);
- tile.mDecodedTile = null;
- }
- mRecycledQueue.push(tile);
- }
- }
-
- private void activateTile(int x, int y, int level) {
- long key = makeTileKey(x, y, level);
- Tile tile = mActiveTiles.get(key);
- if (tile != null) {
- if (tile.mTileState == STATE_IN_QUEUE) {
- tile.mTileState = STATE_ACTIVATED;
- }
- return;
- }
- tile = obtainTile(x, y, level);
- mActiveTiles.put(key, tile);
- }
-
- private Tile getTile(int x, int y, int level) {
- return mActiveTiles.get(makeTileKey(x, y, level));
- }
-
- private static long makeTileKey(int x, int y, int level) {
- long result = x;
- result = (result << 16) | y;
- result = (result << 16) | level;
- return result;
- }
-
- private void uploadTiles(GLCanvas canvas) {
- int quota = UPLOAD_LIMIT;
- Tile tile = null;
- while (quota > 0) {
- synchronized (mQueueLock) {
- tile = mUploadQueue.pop();
- }
- if (tile == null) {
- break;
- }
- if (!tile.isContentValid()) {
- if (tile.mTileState == STATE_DECODED) {
- tile.updateContent(canvas);
- --quota;
- } else {
- Log.w(TAG, "Tile in upload queue has invalid state: " + tile.mTileState);
- }
- }
- }
- if (tile != null) {
- invalidate();
- }
- }
-
- // Draw the tile to a square at canvas that locates at (x, y) and
- // has a side length of length.
- private void drawTile(GLCanvas canvas,
- int tx, int ty, int level, float x, float y, float length) {
- RectF source = mSourceRect;
- RectF target = mTargetRect;
- target.set(x, y, x + length, y + length);
- source.set(0, 0, mTileSize, mTileSize);
-
- Tile tile = getTile(tx, ty, level);
- if (tile != null) {
- if (!tile.isContentValid()) {
- if (tile.mTileState == STATE_DECODED) {
- if (mUploadQuota > 0) {
- --mUploadQuota;
- tile.updateContent(canvas);
- } else {
- mRenderComplete = false;
- }
- } else if (tile.mTileState != STATE_DECODE_FAIL){
- mRenderComplete = false;
- queueForDecode(tile);
- }
- }
- if (drawTile(tile, canvas, source, target)) {
- return;
- }
- }
- if (mPreview != null) {
- int size = mTileSize << level;
- float scaleX = (float) mPreview.getWidth() / mImageWidth;
- float scaleY = (float) mPreview.getHeight() / mImageHeight;
- source.set(tx * scaleX, ty * scaleY, (tx + size) * scaleX,
- (ty + size) * scaleY);
- canvas.drawTexture(mPreview, source, target);
- }
- }
-
- private boolean drawTile(
- Tile tile, GLCanvas canvas, RectF source, RectF target) {
- while (true) {
- if (tile.isContentValid()) {
- canvas.drawTexture(tile, source, target);
- return true;
- }
-
- // Parent can be divided to four quads and tile is one of the four.
- Tile parent = tile.getParentTile();
- if (parent == null) {
- return false;
- }
- if (tile.mX == parent.mX) {
- source.left /= 2f;
- source.right /= 2f;
- } else {
- source.left = (mTileSize + source.left) / 2f;
- source.right = (mTileSize + source.right) / 2f;
- }
- if (tile.mY == parent.mY) {
- source.top /= 2f;
- source.bottom /= 2f;
- } else {
- source.top = (mTileSize + source.top) / 2f;
- source.bottom = (mTileSize + source.bottom) / 2f;
- }
- tile = parent;
- }
- }
-
- private class Tile extends UploadedTexture {
- public int mX;
- public int mY;
- public int mTileLevel;
- public Tile mNext;
- public Bitmap mDecodedTile;
- public volatile int mTileState = STATE_ACTIVATED;
-
- public Tile(int x, int y, int level) {
- mX = x;
- mY = y;
- mTileLevel = level;
- }
-
- @Override
- protected void onFreeBitmap(Bitmap bitmap) {
- sTilePool.release(bitmap);
- }
-
- boolean decode() {
- // Get a tile from the original image. The tile is down-scaled
- // by (1 << mTilelevel) from a region in the original image.
- try {
- Bitmap reuse = sTilePool.acquire();
- if (reuse != null && reuse.getWidth() != mTileSize) {
- reuse = null;
- }
- mDecodedTile = mModel.getTile(mTileLevel, mX, mY, reuse);
- } catch (Throwable t) {
- Log.w(TAG, "fail to decode tile", t);
- }
- return mDecodedTile != null;
- }
-
- @Override
- protected Bitmap onGetBitmap() {
- Utils.assertTrue(mTileState == STATE_DECODED);
-
- // We need to override the width and height, so that we won't
- // draw beyond the boundaries.
- int rightEdge = ((mImageWidth - mX) >> mTileLevel);
- int bottomEdge = ((mImageHeight - mY) >> mTileLevel);
- setSize(Math.min(mTileSize, rightEdge), Math.min(mTileSize, bottomEdge));
-
- Bitmap bitmap = mDecodedTile;
- mDecodedTile = null;
- mTileState = STATE_ACTIVATED;
- return bitmap;
- }
-
- // We override getTextureWidth() and getTextureHeight() here, so the
- // texture can be re-used for different tiles regardless of the actual
- // size of the tile (which may be small because it is a tile at the
- // boundary).
- @Override
- public int getTextureWidth() {
- return mTileSize;
- }
-
- @Override
- public int getTextureHeight() {
- return mTileSize;
- }
-
- public void update(int x, int y, int level) {
- mX = x;
- mY = y;
- mTileLevel = level;
- invalidateContent();
- }
-
- public Tile getParentTile() {
- if (mTileLevel + 1 == mLevelCount) {
- return null;
- }
- int size = mTileSize << (mTileLevel + 1);
- int x = size * (mX / size);
- int y = size * (mY / size);
- return getTile(x, y, mTileLevel + 1);
- }
-
- @Override
- public String toString() {
- return String.format("tile(%s, %s, %s / %s)",
- mX / mTileSize, mY / mTileSize, mLevel, mLevelCount);
- }
- }
-
- private static class TileQueue {
- private Tile mHead;
-
- public Tile pop() {
- Tile tile = mHead;
- if (tile != null) {
- mHead = tile.mNext;
- }
- return tile;
- }
-
- public boolean push(Tile tile) {
- if (contains(tile)) {
- Log.w(TAG, "Attempting to add a tile already in the queue!");
- return false;
- }
- boolean wasEmpty = mHead == null;
- tile.mNext = mHead;
- mHead = tile;
- return wasEmpty;
- }
-
- private boolean contains(Tile tile) {
- Tile other = mHead;
- while (other != null) {
- if (other == tile) {
- return true;
- }
- other = other.mNext;
- }
- return false;
- }
-
- public void clean() {
- mHead = null;
- }
- }
-
- private class TileDecoder extends Thread {
-
- public void finishAndWait() {
- interrupt();
- try {
- join();
- } catch (InterruptedException e) {
- Log.w(TAG, "Interrupted while waiting for TileDecoder thread to finish!");
- }
- }
-
- private Tile waitForTile() throws InterruptedException {
- synchronized (mQueueLock) {
- while (true) {
- Tile tile = mDecodeQueue.pop();
- if (tile != null) {
- return tile;
- }
- mQueueLock.wait();
- }
- }
- }
-
- @Override
- public void run() {
- try {
- while (!isInterrupted()) {
- Tile tile = waitForTile();
- decodeTile(tile);
- }
- } catch (InterruptedException ex) {
- // We were finished
- }
- }
-
- }
-}
diff --git a/src/com/android/photos/views/TiledImageView.java b/src/com/android/photos/views/TiledImageView.java
deleted file mode 100644
index af4199c91..000000000
--- a/src/com/android/photos/views/TiledImageView.java
+++ /dev/null
@@ -1,386 +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.photos.views;
-
-import android.annotation.SuppressLint;
-import android.annotation.TargetApi;
-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;
-import android.os.Build;
-import android.util.AttributeSet;
-import android.view.Choreographer;
-import android.view.Choreographer.FrameCallback;
-import android.view.View;
-import android.widget.FrameLayout;
-
-import com.android.gallery3d.glrenderer.BasicTexture;
-import com.android.gallery3d.glrenderer.GLES20Canvas;
-import com.android.photos.views.TiledImageRenderer.TileSource;
-
-import javax.microedition.khronos.egl.EGLConfig;
-import javax.microedition.khronos.opengles.GL10;
-
-/**
- * Shows an image using {@link TiledImageRenderer} using either {@link GLSurfaceView}
- * or {@link BlockingGLTextureView}.
- */
-public class TiledImageView extends FrameLayout {
-
- private static final boolean USE_TEXTURE_VIEW = false;
- private static final boolean IS_SUPPORTED =
- Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
- private static final boolean USE_CHOREOGRAPHER =
- Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
-
- private BlockingGLTextureView mTextureView;
- private GLSurfaceView mGLSurfaceView;
- private boolean mInvalPending = false;
- private FrameCallback mFrameCallback;
-
- protected static class ImageRendererWrapper {
- // Guarded by locks
- public float scale;
- public int centerX, centerY;
- public int rotation;
- public TileSource source;
- Runnable isReadyCallback;
-
- // GL thread only
- TiledImageRenderer image;
- }
-
- private float[] mValues = new float[9];
-
- // -------------------------
- // Guarded by mLock
- // -------------------------
- protected Object mLock = new Object();
- protected ImageRendererWrapper mRenderer;
-
- public static boolean isTilingSupported() {
- return IS_SUPPORTED;
- }
-
- public TiledImageView(Context context) {
- this(context, null);
- }
-
- public TiledImageView(Context context, AttributeSet attrs) {
- super(context, attrs);
- if (!IS_SUPPORTED) {
- return;
- }
-
- mRenderer = new ImageRendererWrapper();
- mRenderer.image = new TiledImageRenderer(this);
- View view;
- if (USE_TEXTURE_VIEW) {
- mTextureView = new BlockingGLTextureView(context);
- mTextureView.setRenderer(new TileRenderer());
- view = mTextureView;
- } else {
- mGLSurfaceView = new GLSurfaceView(context);
- mGLSurfaceView.setEGLContextClientVersion(2);
- mGLSurfaceView.setRenderer(new TileRenderer());
- mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
- view = mGLSurfaceView;
- }
- addView(view, new LayoutParams(
- LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
- //setTileSource(new ColoredTiles());
- }
-
- public void destroy() {
- if (!IS_SUPPORTED) {
- return;
- }
- if (USE_TEXTURE_VIEW) {
- mTextureView.destroy();
- } else {
- mGLSurfaceView.queueEvent(mFreeTextures);
- }
- }
-
- private Runnable mFreeTextures = new Runnable() {
-
- @Override
- public void run() {
- mRenderer.image.freeTextures();
- }
- };
-
- public void onPause() {
- if (!IS_SUPPORTED) {
- return;
- }
- if (!USE_TEXTURE_VIEW) {
- mGLSurfaceView.onPause();
- }
- }
-
- public void onResume() {
- if (!IS_SUPPORTED) {
- return;
- }
- if (!USE_TEXTURE_VIEW) {
- mGLSurfaceView.onResume();
- }
- }
-
- public void setTileSource(TileSource source, Runnable isReadyCallback) {
- if (!IS_SUPPORTED) {
- return;
- }
- synchronized (mLock) {
- mRenderer.source = source;
- mRenderer.isReadyCallback = isReadyCallback;
- mRenderer.centerX = source != null ? source.getImageWidth() / 2 : 0;
- mRenderer.centerY = source != null ? source.getImageHeight() / 2 : 0;
- mRenderer.rotation = source != null ? source.getRotation() : 0;
- mRenderer.scale = 0;
- updateScaleIfNecessaryLocked(mRenderer);
- }
- invalidate();
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right,
- int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- if (!IS_SUPPORTED) {
- return;
- }
- synchronized (mLock) {
- updateScaleIfNecessaryLocked(mRenderer);
- }
- }
-
- private void updateScaleIfNecessaryLocked(ImageRendererWrapper renderer) {
- if (renderer == null || renderer.source == null
- || renderer.scale > 0 || getWidth() == 0) {
- return;
- }
- renderer.scale = Math.min(
- (float) getWidth() / (float) renderer.source.getImageWidth(),
- (float) getHeight() / (float) renderer.source.getImageHeight());
- }
-
- @Override
- protected void dispatchDraw(Canvas canvas) {
- if (!IS_SUPPORTED) {
- return;
- }
- if (USE_TEXTURE_VIEW) {
- mTextureView.render();
- }
- super.dispatchDraw(canvas);
- }
-
- @SuppressLint("NewApi")
- @Override
- public void setTranslationX(float translationX) {
- if (!IS_SUPPORTED) {
- return;
- }
- super.setTranslationX(translationX);
- }
-
- @Override
- public void invalidate() {
- if (!IS_SUPPORTED) {
- return;
- }
- if (USE_TEXTURE_VIEW) {
- super.invalidate();
- mTextureView.invalidate();
- } else {
- if (USE_CHOREOGRAPHER) {
- invalOnVsync();
- } else {
- mGLSurfaceView.requestRender();
- }
- }
- }
-
- @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
- private void invalOnVsync() {
- if (!mInvalPending) {
- mInvalPending = true;
- if (mFrameCallback == null) {
- mFrameCallback = new FrameCallback() {
- @Override
- public void doFrame(long frameTimeNanos) {
- mInvalPending = false;
- mGLSurfaceView.requestRender();
- }
- };
- }
- Choreographer.getInstance().postFrameCallback(mFrameCallback);
- }
- }
-
- private RectF mTempRectF = new RectF();
- public void positionFromMatrix(Matrix matrix) {
- if (!IS_SUPPORTED) {
- return;
- }
- if (mRenderer.source != null) {
- final int rotation = mRenderer.source.getRotation();
- final boolean swap = !(rotation % 180 == 0);
- final int width = swap ? mRenderer.source.getImageHeight()
- : mRenderer.source.getImageWidth();
- final int height = swap ? mRenderer.source.getImageWidth()
- : mRenderer.source.getImageHeight();
- mTempRectF.set(0, 0, width, height);
- matrix.mapRect(mTempRectF);
- matrix.getValues(mValues);
- int cx = width / 2;
- int cy = height / 2;
- float scale = mValues[Matrix.MSCALE_X];
- int xoffset = Math.round((getWidth() - mTempRectF.width()) / 2 / scale);
- int yoffset = Math.round((getHeight() - mTempRectF.height()) / 2 / scale);
- if (rotation == 90 || rotation == 180) {
- cx += (mTempRectF.left / scale) - xoffset;
- } else {
- cx -= (mTempRectF.left / scale) - xoffset;
- }
- if (rotation == 180 || rotation == 270) {
- cy += (mTempRectF.top / scale) - yoffset;
- } else {
- cy -= (mTempRectF.top / scale) - yoffset;
- }
- mRenderer.scale = scale;
- mRenderer.centerX = swap ? cy : cx;
- mRenderer.centerY = swap ? cx : cy;
- invalidate();
- }
- }
-
- private class TileRenderer implements Renderer {
-
- private GLES20Canvas mCanvas;
-
- @Override
- public void onSurfaceCreated(GL10 gl, EGLConfig config) {
- mCanvas = new GLES20Canvas();
- BasicTexture.invalidateAllTextures();
- mRenderer.image.setModel(mRenderer.source, mRenderer.rotation);
- }
-
- @Override
- public void onSurfaceChanged(GL10 gl, int width, int height) {
- mCanvas.setSize(width, height);
- mRenderer.image.setViewSize(width, height);
- }
-
- @Override
- public void onDrawFrame(GL10 gl) {
- mCanvas.clearBuffer();
- Runnable readyCallback;
- synchronized (mLock) {
- readyCallback = mRenderer.isReadyCallback;
- mRenderer.image.setModel(mRenderer.source, mRenderer.rotation);
- mRenderer.image.setPosition(mRenderer.centerX, mRenderer.centerY,
- mRenderer.scale);
- }
- boolean complete = mRenderer.image.draw(mCanvas);
- if (complete && readyCallback != null) {
- synchronized (mLock) {
- // Make sure we don't trample on a newly set callback/source
- // if it changed while we were rendering
- if (mRenderer.isReadyCallback == readyCallback) {
- mRenderer.isReadyCallback = null;
- }
- }
- if (readyCallback != null) {
- post(readyCallback);
- }
- }
- }
-
- }
-
- @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;
- }
- }
-}