1 /*
2  * Copyright (C) 2010 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 package com.android.launcher2;
17 
18 import android.app.Activity;
19 import android.app.Dialog;
20 import android.app.DialogFragment;
21 import android.app.WallpaperManager;
22 import android.content.Context;
23 import android.content.DialogInterface;
24 import android.content.res.Resources;
25 import android.graphics.Bitmap;
26 import android.graphics.BitmapFactory;
27 import android.graphics.Canvas;
28 import android.graphics.ColorFilter;
29 import android.graphics.Matrix;
30 import android.graphics.drawable.BitmapDrawable;
31 import android.graphics.drawable.Drawable;
32 import android.os.AsyncTask;
33 import android.os.Bundle;
34 import android.util.Log;
35 import android.view.LayoutInflater;
36 import android.view.View;
37 import android.view.View.OnClickListener;
38 import android.view.ViewGroup;
39 import android.widget.AdapterView;
40 import android.widget.BaseAdapter;
41 import android.widget.Gallery;
42 import android.widget.ImageView;
43 import android.widget.ListAdapter;
44 import android.widget.SpinnerAdapter;
45 
46 import com.android.launcher.R;
47 
48 import java.io.IOException;
49 import java.util.ArrayList;
50 
51 public class WallpaperChooserDialogFragment extends DialogFragment implements
52         AdapterView.OnItemSelectedListener, AdapterView.OnItemClickListener {
53 
54     private static final String TAG = "Launcher.WallpaperChooserDialogFragment";
55     private static final String EMBEDDED_KEY = "com.android.launcher2."
56             + "WallpaperChooserDialogFragment.EMBEDDED_KEY";
57 
58     private boolean mEmbedded;
59 
60     private ArrayList<Integer> mThumbs;
61     private ArrayList<Integer> mImages;
62     private WallpaperLoader mLoader;
63     private WallpaperDrawable mWallpaperDrawable = new WallpaperDrawable();
64 
newInstance()65     public static WallpaperChooserDialogFragment newInstance() {
66         WallpaperChooserDialogFragment fragment = new WallpaperChooserDialogFragment();
67         fragment.setCancelable(true);
68         return fragment;
69     }
70 
71     @Override
onCreate(Bundle savedInstanceState)72     public void onCreate(Bundle savedInstanceState) {
73         super.onCreate(savedInstanceState);
74         if (savedInstanceState != null && savedInstanceState.containsKey(EMBEDDED_KEY)) {
75             mEmbedded = savedInstanceState.getBoolean(EMBEDDED_KEY);
76         } else {
77             mEmbedded = isInLayout();
78         }
79     }
80 
81     @Override
onSaveInstanceState(Bundle outState)82     public void onSaveInstanceState(Bundle outState) {
83         outState.putBoolean(EMBEDDED_KEY, mEmbedded);
84     }
85 
cancelLoader()86     private void cancelLoader() {
87         if (mLoader != null && mLoader.getStatus() != WallpaperLoader.Status.FINISHED) {
88             mLoader.cancel(true);
89             mLoader = null;
90         }
91     }
92 
93     @Override
onDetach()94     public void onDetach() {
95         super.onDetach();
96 
97         cancelLoader();
98     }
99 
100     @Override
onDestroy()101     public void onDestroy() {
102         super.onDestroy();
103 
104         cancelLoader();
105     }
106 
107     @Override
onDismiss(DialogInterface dialog)108     public void onDismiss(DialogInterface dialog) {
109         super.onDismiss(dialog);
110         /* On orientation changes, the dialog is effectively "dismissed" so this is called
111          * when the activity is no longer associated with this dying dialog fragment. We
112          * should just safely ignore this case by checking if getActivity() returns null
113          */
114         Activity activity = getActivity();
115         if (activity != null) {
116             activity.finish();
117         }
118     }
119 
120     /* This will only be called when in XLarge mode, since this Fragment is invoked like
121      * a dialog in that mode
122      */
123     @Override
onCreateDialog(Bundle savedInstanceState)124     public Dialog onCreateDialog(Bundle savedInstanceState) {
125         findWallpapers();
126 
127         return null;
128     }
129 
130     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)131     public View onCreateView(LayoutInflater inflater, ViewGroup container,
132             Bundle savedInstanceState) {
133         findWallpapers();
134 
135         /* If this fragment is embedded in the layout of this activity, then we should
136          * generate a view to display. Otherwise, a dialog will be created in
137          * onCreateDialog()
138          */
139         if (mEmbedded) {
140             View view = inflater.inflate(R.layout.wallpaper_chooser, container, false);
141             view.setBackground(mWallpaperDrawable);
142 
143             final Gallery gallery = (Gallery) view.findViewById(R.id.gallery);
144             gallery.setCallbackDuringFling(false);
145             gallery.setOnItemSelectedListener(this);
146             gallery.setAdapter(new ImageAdapter(getActivity()));
147 
148             View setButton = view.findViewById(R.id.set);
149             setButton.setOnClickListener(new OnClickListener() {
150                 @Override
151                 public void onClick(View v) {
152                     selectWallpaper(gallery.getSelectedItemPosition());
153                 }
154             });
155             return view;
156         }
157         return null;
158     }
159 
selectWallpaper(int position)160     private void selectWallpaper(int position) {
161         try {
162             WallpaperManager wpm = (WallpaperManager) getActivity().getSystemService(
163                     Context.WALLPAPER_SERVICE);
164             wpm.setResource(mImages.get(position));
165             Activity activity = getActivity();
166             activity.setResult(Activity.RESULT_OK);
167             activity.finish();
168         } catch (IOException e) {
169             Log.e(TAG, "Failed to set wallpaper: " + e);
170         }
171     }
172 
173     // Click handler for the Dialog's GridView
174     @Override
onItemClick(AdapterView<?> parent, View view, int position, long id)175     public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
176         selectWallpaper(position);
177     }
178 
179     // Selection handler for the embedded Gallery view
180     @Override
onItemSelected(AdapterView<?> parent, View view, int position, long id)181     public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
182         if (mLoader != null && mLoader.getStatus() != WallpaperLoader.Status.FINISHED) {
183             mLoader.cancel();
184         }
185         mLoader = (WallpaperLoader) new WallpaperLoader().execute(position);
186     }
187 
188     @Override
onNothingSelected(AdapterView<?> parent)189     public void onNothingSelected(AdapterView<?> parent) {
190     }
191 
findWallpapers()192     private void findWallpapers() {
193         mThumbs = new ArrayList<Integer>(24);
194         mImages = new ArrayList<Integer>(24);
195 
196         final Resources resources = getResources();
197         // Context.getPackageName() may return the "original" package name,
198         // com.android.launcher2; Resources needs the real package name,
199         // com.android.launcher. So we ask Resources for what it thinks the
200         // package name should be.
201         final String packageName = resources.getResourcePackageName(R.array.wallpapers);
202 
203         addWallpapers(resources, packageName, R.array.wallpapers);
204         addWallpapers(resources, packageName, R.array.extra_wallpapers);
205     }
206 
addWallpapers(Resources resources, String packageName, int list)207     private void addWallpapers(Resources resources, String packageName, int list) {
208         final String[] extras = resources.getStringArray(list);
209         for (String extra : extras) {
210             int res = resources.getIdentifier(extra, "drawable", packageName);
211             if (res != 0) {
212                 final int thumbRes = resources.getIdentifier(extra + "_small",
213                         "drawable", packageName);
214 
215                 if (thumbRes != 0) {
216                     mThumbs.add(thumbRes);
217                     mImages.add(res);
218                     // Log.d(TAG, "add: [" + packageName + "]: " + extra + " (" + res + ")");
219                 }
220             }
221         }
222     }
223 
224     private class ImageAdapter extends BaseAdapter implements ListAdapter, SpinnerAdapter {
225         private LayoutInflater mLayoutInflater;
226 
ImageAdapter(Activity activity)227         ImageAdapter(Activity activity) {
228             mLayoutInflater = activity.getLayoutInflater();
229         }
230 
getCount()231         public int getCount() {
232             return mThumbs.size();
233         }
234 
getItem(int position)235         public Object getItem(int position) {
236             return position;
237         }
238 
getItemId(int position)239         public long getItemId(int position) {
240             return position;
241         }
242 
getView(int position, View convertView, ViewGroup parent)243         public View getView(int position, View convertView, ViewGroup parent) {
244             View view;
245 
246             if (convertView == null) {
247                 view = mLayoutInflater.inflate(R.layout.wallpaper_item, parent, false);
248             } else {
249                 view = convertView;
250             }
251 
252             ImageView image = (ImageView) view.findViewById(R.id.wallpaper_image);
253 
254             int thumbRes = mThumbs.get(position);
255             image.setImageResource(thumbRes);
256             Drawable thumbDrawable = image.getDrawable();
257             if (thumbDrawable != null) {
258                 thumbDrawable.setDither(true);
259             } else {
260                 Log.e(TAG, "Error decoding thumbnail resId=" + thumbRes + " for wallpaper #"
261                         + position);
262             }
263 
264             return view;
265         }
266     }
267 
268     class WallpaperLoader extends AsyncTask<Integer, Void, Bitmap> {
WallpaperLoader()269         WallpaperLoader() {
270         }
271 
272         @Override
doInBackground(Integer... params)273         protected Bitmap doInBackground(Integer... params) {
274             if (isCancelled()) return null;
275             try {
276                 final Drawable d = getResources().getDrawable(mImages.get(params[0]));
277                 if (d instanceof BitmapDrawable) {
278                     return ((BitmapDrawable)d).getBitmap();
279                 }
280                 return null;
281             } catch (OutOfMemoryError e) {
282                 Log.w(TAG, String.format(
283                         "Out of memory trying to load wallpaper res=%08x", params[0]),
284                         e);
285                 return null;
286             }
287         }
288 
289         @Override
onPostExecute(Bitmap b)290         protected void onPostExecute(Bitmap b) {
291             if (b == null) return;
292 
293             if (!isCancelled()) {
294                 View v = getView();
295                 if (v != null) {
296                     mWallpaperDrawable.setBitmap(b);
297                     v.postInvalidate();
298                 } else {
299                     mWallpaperDrawable.setBitmap(null);
300                 }
301                 mLoader = null;
302             } else {
303                b.recycle();
304             }
305         }
306 
cancel()307         void cancel() {
308             super.cancel(true);
309         }
310     }
311 
312     /**
313      * Custom drawable that centers the bitmap fed to it.
314      */
315     static class WallpaperDrawable extends Drawable {
316 
317         Bitmap mBitmap;
318         int mIntrinsicWidth;
319         int mIntrinsicHeight;
320         Matrix mMatrix;
321 
setBitmap(Bitmap bitmap)322         /* package */void setBitmap(Bitmap bitmap) {
323             mBitmap = bitmap;
324             if (mBitmap == null)
325                 return;
326             mIntrinsicWidth = mBitmap.getWidth();
327             mIntrinsicHeight = mBitmap.getHeight();
328             mMatrix = null;
329         }
330 
331         @Override
draw(Canvas canvas)332         public void draw(Canvas canvas) {
333             if (mBitmap == null) return;
334 
335             if (mMatrix == null) {
336                 final int vwidth = canvas.getWidth();
337                 final int vheight = canvas.getHeight();
338                 final int dwidth = mIntrinsicWidth;
339                 final int dheight = mIntrinsicHeight;
340 
341                 float scale = 1.0f;
342 
343                 if (dwidth < vwidth || dheight < vheight) {
344                     scale = Math.max((float) vwidth / (float) dwidth,
345                             (float) vheight / (float) dheight);
346                 }
347 
348                 float dx = (vwidth - dwidth * scale) * 0.5f + 0.5f;
349                 float dy = (vheight - dheight * scale) * 0.5f + 0.5f;
350 
351                 mMatrix = new Matrix();
352                 mMatrix.setScale(scale, scale);
353                 mMatrix.postTranslate((int) dx, (int) dy);
354             }
355 
356             canvas.drawBitmap(mBitmap, mMatrix, null);
357         }
358 
359         @Override
getOpacity()360         public int getOpacity() {
361             return android.graphics.PixelFormat.OPAQUE;
362         }
363 
364         @Override
setAlpha(int alpha)365         public void setAlpha(int alpha) {
366             // Ignore
367         }
368 
369         @Override
setColorFilter(ColorFilter cf)370         public void setColorFilter(ColorFilter cf) {
371             // Ignore
372         }
373     }
374 }
375