1 /*
2  * Copyright (C) 2013 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.launcher3;
18 
19 import android.content.ContentValues;
20 import android.content.Context;
21 import android.database.Cursor;
22 import android.database.sqlite.SQLiteDatabase;
23 import android.database.sqlite.SQLiteOpenHelper;
24 import android.graphics.Bitmap;
25 import android.graphics.BitmapFactory;
26 import android.graphics.drawable.BitmapDrawable;
27 import android.graphics.drawable.Drawable;
28 import android.net.Uri;
29 import android.text.TextUtils;
30 import android.util.Log;
31 import android.util.Pair;
32 import android.view.LayoutInflater;
33 import android.view.View;
34 import android.view.ViewGroup;
35 import android.widget.BaseAdapter;
36 import android.widget.ListAdapter;
37 
38 import com.android.launcher3.WallpaperCropActivity.CropViewScaleAndOffsetProvider;
39 import com.android.photos.views.TiledImageRenderer.TileSource;
40 
41 import java.io.ByteArrayInputStream;
42 import java.io.File;
43 import java.io.FileOutputStream;
44 import java.io.IOException;
45 import java.io.InputStream;
46 import java.util.ArrayList;
47 
48 
49 public class SavedWallpaperImages extends BaseAdapter implements ListAdapter {
50     private static String TAG = "Launcher3.SavedWallpaperImages";
51     private ImageDb mDb;
52     ArrayList<SavedWallpaperTile> mImages;
53     Context mContext;
54     LayoutInflater mLayoutInflater;
55 
56     public static class SavedWallpaperTile extends WallpaperPickerActivity.FileWallpaperInfo {
57         private int mDbId;
58 
59         // three floats representing scale, centerX and centerY of the crop view in order.
60         private Float[] mExtras;
SavedWallpaperTile(int dbId, File target, Drawable thumb, Float[] extras)61         public SavedWallpaperTile(int dbId, File target, Drawable thumb, Float[] extras) {
62             super(target, thumb);
63             mDbId = dbId;
64             mExtras = extras != null && extras.length == 3 ? extras : null;
65         }
66 
67         @Override
onDelete(WallpaperPickerActivity a)68         public void onDelete(WallpaperPickerActivity a) {
69             a.getSavedImages().deleteImage(mDbId);
70         }
71 
72         @Override
getCropViewScaleAndOffsetProvider()73         protected CropViewScaleAndOffsetProvider getCropViewScaleAndOffsetProvider() {
74             if (mExtras != null) {
75                 return new CropViewScaleAndOffsetProvider() {
76                     @Override
77                     public void updateCropView(WallpaperCropActivity a, TileSource src) {
78                         a.mCropView.setScaleAndCenter(mExtras[0], mExtras[1], mExtras[2]);
79                     }
80                 };
81             }
82             return null;
83         }
84 
85         @Override
onSave(final WallpaperPickerActivity a)86         public void onSave(final WallpaperPickerActivity a) {
87             if (mExtras == null) {
88                 super.onSave(a);
89             } else {
90                 boolean shouldFadeOutOnFinish = a.getWallpaperParallaxOffset() == 0f;
91                 a.cropImageAndSetWallpaper(Uri.fromFile(mFile), null, true, shouldFadeOutOnFinish);
92             }
93         }
94     }
95 
96     public SavedWallpaperImages(Context context) {
97         // We used to store the saved images in the cache directory, but that meant they'd get
98         // deleted sometimes-- move them to the data directory
99         ImageDb.moveFromCacheDirectoryIfNecessary(context);
100         mDb = new ImageDb(context);
101         mContext = context;
102         mLayoutInflater = LayoutInflater.from(context);
103     }
104 
105     public void loadThumbnailsAndImageIdList() {
106         mImages = new ArrayList<SavedWallpaperTile>();
107         SQLiteDatabase db = mDb.getReadableDatabase();
108         Cursor result = db.query(ImageDb.TABLE_NAME,
109                 new String[] { ImageDb.COLUMN_ID,
110                     ImageDb.COLUMN_IMAGE_THUMBNAIL_FILENAME,
111                     ImageDb.COLUMN_IMAGE_FILENAME,
112                     ImageDb.COLUMN_EXTRAS}, // cols to return
113                 null, // select query
114                 null, // args to select query
115                 null,
116                 null,
117                 ImageDb.COLUMN_ID + " DESC",
118                 null);
119 
120         while (result.moveToNext()) {
121             String filename = result.getString(1);
122             File file = new File(mContext.getFilesDir(), filename);
123 
124             Bitmap thumb = BitmapFactory.decodeFile(file.getAbsolutePath());
125             if (thumb != null) {
126 
127                 Float[] extras = null;
128                 String extraStr = result.getString(3);
129                 if (extraStr != null) {
130                     String[] parts = extraStr.split(",");
131                     extras = new Float[parts.length];
132                     for (int i = 0; i < parts.length; i++) {
133                         try {
134                             extras[i] = Float.parseFloat(parts[i]);
135                         } catch (Exception e) {
136                             extras = null;
137                             break;
138                         }
139                     }
140                 }
141 
142                 mImages.add(new SavedWallpaperTile(result.getInt(0),
143                         new File(mContext.getFilesDir(), result.getString(2)),
144                         new BitmapDrawable(thumb), extras));
145             }
146         }
147         result.close();
148     }
149 
150     public int getCount() {
151         return mImages.size();
152     }
153 
154     public SavedWallpaperTile getItem(int position) {
155         return mImages.get(position);
156     }
157 
158     public long getItemId(int position) {
159         return position;
160     }
161 
162     public View getView(int position, View convertView, ViewGroup parent) {
163         Drawable thumbDrawable = mImages.get(position).mThumb;
164         if (thumbDrawable == null) {
165             Log.e(TAG, "Error decoding thumbnail for wallpaper #" + position);
166         }
167         return WallpaperPickerActivity.createImageTileView(
168                 mLayoutInflater, convertView, parent, thumbDrawable);
169     }
170 
171     private Pair<String, String> getImageFilenames(int id) {
172         SQLiteDatabase db = mDb.getReadableDatabase();
173         Cursor result = db.query(ImageDb.TABLE_NAME,
174                 new String[] { ImageDb.COLUMN_IMAGE_THUMBNAIL_FILENAME,
175                     ImageDb.COLUMN_IMAGE_FILENAME }, // cols to return
176                 ImageDb.COLUMN_ID + " = ?", // select query
177                 new String[] { Integer.toString(id) }, // args to select query
178                 null,
179                 null,
180                 null,
181                 null);
182         if (result.getCount() > 0) {
183             result.moveToFirst();
184             String thumbFilename = result.getString(0);
185             String imageFilename = result.getString(1);
186             result.close();
187             return new Pair<String, String>(thumbFilename, imageFilename);
188         } else {
189             return null;
190         }
191     }
192 
193     public void deleteImage(int id) {
194         Pair<String, String> filenames = getImageFilenames(id);
195         File imageFile = new File(mContext.getFilesDir(), filenames.first);
196         imageFile.delete();
197         File thumbFile = new File(mContext.getFilesDir(), filenames.second);
198         thumbFile.delete();
199         SQLiteDatabase db = mDb.getWritableDatabase();
200         db.delete(ImageDb.TABLE_NAME,
201                 ImageDb.COLUMN_ID + " = ?", // SELECT query
202                 new String[] {
203                     Integer.toString(id) // args to SELECT query
204                 });
205     }
206 
207     public void writeImage(Bitmap thumbnail, byte[] imageBytes) {
208         try {
209             writeImage(thumbnail, new ByteArrayInputStream(imageBytes), null);
210         } catch (IOException e) {
211             Log.e(TAG, "Failed writing images to storage " + e);
212         }
213     }
214 
215     public void writeImage(Bitmap thumbnail, Uri uri, Float[] extras) {
216         try {
217             writeImage(thumbnail, mContext.getContentResolver().openInputStream(uri), extras);
218         } catch (IOException e) {
219             Log.e(TAG, "Failed writing images to storage " + e);
220         }
221     }
222 
223     private void writeImage(Bitmap thumbnail, InputStream in, Float[] extras) throws IOException {
224         File imageFile = File.createTempFile("wallpaper", "", mContext.getFilesDir());
225         FileOutputStream imageFileStream =
226                 mContext.openFileOutput(imageFile.getName(), Context.MODE_PRIVATE);
227         byte[] buf = new byte[4096];    // 4k
228         int len;
229         while ((len = in.read(buf)) > 0) {
230             imageFileStream.write(buf, 0, len);
231         }
232         imageFileStream.close();
233         in.close();
234 
235         File thumbFile = File.createTempFile("wallpaperthumb", "", mContext.getFilesDir());
236         FileOutputStream thumbFileStream =
237                 mContext.openFileOutput(thumbFile.getName(), Context.MODE_PRIVATE);
238         thumbnail.compress(Bitmap.CompressFormat.JPEG, 95, thumbFileStream);
239         thumbFileStream.close();
240 
241         SQLiteDatabase db = mDb.getWritableDatabase();
242         ContentValues values = new ContentValues();
243         values.put(ImageDb.COLUMN_IMAGE_THUMBNAIL_FILENAME, thumbFile.getName());
244         values.put(ImageDb.COLUMN_IMAGE_FILENAME, imageFile.getName());
245         if (extras != null) {
246             values.put(ImageDb.COLUMN_EXTRAS, TextUtils.join(",", extras));
247         }
248         db.insert(ImageDb.TABLE_NAME, null, values);
249     }
250 
251     static class ImageDb extends SQLiteOpenHelper {
252         final static int DB_VERSION = 2;
253         final static String TABLE_NAME = "saved_wallpaper_images";
254         final static String COLUMN_ID = "id";
255         final static String COLUMN_IMAGE_THUMBNAIL_FILENAME = "image_thumbnail";
256         final static String COLUMN_IMAGE_FILENAME = "image";
257         final static String COLUMN_EXTRAS = "extras";
258 
259         Context mContext;
260 
261         public ImageDb(Context context) {
262             super(context, context.getDatabasePath(LauncherFiles.WALLPAPER_IMAGES_DB).getPath(),
263                     null, DB_VERSION);
264             // Store the context for later use
265             mContext = context;
266         }
267 
268         public static void moveFromCacheDirectoryIfNecessary(Context context) {
269             // We used to store the saved images in the cache directory, but that meant they'd get
270             // deleted sometimes-- move them to the data directory
271             File oldSavedImagesFile = new File(context.getCacheDir(),
272                     LauncherFiles.WALLPAPER_IMAGES_DB);
273             File savedImagesFile = context.getDatabasePath(LauncherFiles.WALLPAPER_IMAGES_DB);
274             if (oldSavedImagesFile.exists()) {
275                 oldSavedImagesFile.renameTo(savedImagesFile);
276             }
277         }
278         @Override
279         public void onCreate(SQLiteDatabase database) {
280             database.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" +
281                     COLUMN_ID + " INTEGER NOT NULL, " +
282                     COLUMN_IMAGE_THUMBNAIL_FILENAME + " TEXT NOT NULL, " +
283                     COLUMN_IMAGE_FILENAME + " TEXT NOT NULL, " +
284                     COLUMN_EXTRAS + " TEXT, " +
285                     "PRIMARY KEY (" + COLUMN_ID + " ASC) " +
286                     ");");
287         }
288 
289         @Override
290         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
291             if (oldVersion == 1) {
292                 // Add extras column
293                 db.execSQL("ALTER TABLE " + TABLE_NAME + " ADD COLUMN " + COLUMN_EXTRAS + " TEXT;");
294             } else if (oldVersion != newVersion) {
295                 // Delete all the records; they'll be repopulated as this is a cache
296                 db.execSQL("DELETE FROM " + TABLE_NAME);
297                 onCreate(db);
298             }
299         }
300     }
301 }
302