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