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
17package com.android.launcher3;
18
19import android.animation.LayoutTransition;
20import android.annotation.TargetApi;
21import android.app.ActionBar;
22import android.app.Activity;
23import android.app.WallpaperInfo;
24import android.app.WallpaperManager;
25import android.content.Context;
26import android.content.Intent;
27import android.content.pm.ApplicationInfo;
28import android.content.pm.PackageManager;
29import android.content.res.Resources;
30import android.database.Cursor;
31import android.database.DataSetObserver;
32import android.graphics.Bitmap;
33import android.graphics.BitmapFactory;
34import android.graphics.Canvas;
35import android.graphics.Matrix;
36import android.graphics.Point;
37import android.graphics.PorterDuff;
38import android.graphics.Rect;
39import android.graphics.RectF;
40import android.graphics.drawable.BitmapDrawable;
41import android.graphics.drawable.Drawable;
42import android.graphics.drawable.LevelListDrawable;
43import android.net.Uri;
44import android.os.AsyncTask;
45import android.os.Build;
46import android.os.Bundle;
47import android.provider.MediaStore;
48import android.util.Log;
49import android.util.Pair;
50import android.view.ActionMode;
51import android.view.LayoutInflater;
52import android.view.Menu;
53import android.view.MenuInflater;
54import android.view.MenuItem;
55import android.view.View;
56import android.view.View.OnClickListener;
57import android.view.View.OnLayoutChangeListener;
58import android.view.ViewGroup;
59import android.view.ViewPropertyAnimator;
60import android.view.ViewTreeObserver;
61import android.view.ViewTreeObserver.OnGlobalLayoutListener;
62import android.view.WindowManager;
63import android.view.animation.AccelerateInterpolator;
64import android.view.animation.DecelerateInterpolator;
65import android.widget.ArrayAdapter;
66import android.widget.BaseAdapter;
67import android.widget.FrameLayout;
68import android.widget.HorizontalScrollView;
69import android.widget.ImageView;
70import android.widget.LinearLayout;
71import android.widget.Toast;
72
73import com.android.photos.BitmapRegionTileSource;
74import com.android.photos.BitmapRegionTileSource.BitmapSource;
75
76import java.io.File;
77import java.io.FileOutputStream;
78import java.io.IOException;
79import java.util.ArrayList;
80
81public class WallpaperPickerActivity extends WallpaperCropActivity {
82    static final String TAG = "Launcher.WallpaperPickerActivity";
83
84    public static final int IMAGE_PICK = 5;
85    public static final int PICK_WALLPAPER_THIRD_PARTY_ACTIVITY = 6;
86    public static final int PICK_LIVE_WALLPAPER = 7;
87    private static final String TEMP_WALLPAPER_TILES = "TEMP_WALLPAPER_TILES";
88    private static final String SELECTED_INDEX = "SELECTED_INDEX";
89    private static final int FLAG_POST_DELAY_MILLIS = 200;
90
91    private View mSelectedTile;
92    private boolean mIgnoreNextTap;
93    private OnClickListener mThumbnailOnClickListener;
94
95    private LinearLayout mWallpapersView;
96    private View mWallpaperStrip;
97
98    private ActionMode.Callback mActionModeCallback;
99    private ActionMode mActionMode;
100
101    private View.OnLongClickListener mLongClickListener;
102
103    ArrayList<Uri> mTempWallpaperTiles = new ArrayList<Uri>();
104    private SavedWallpaperImages mSavedImages;
105    private WallpaperInfo mLiveWallpaperInfoOnPickerLaunch;
106    private int mSelectedIndex = -1;
107    private WallpaperInfo mLastClickedLiveWallpaperInfo;
108
109    public static abstract class WallpaperTileInfo {
110        protected View mView;
111        public Drawable mThumb;
112
113        public void setView(View v) {
114            mView = v;
115        }
116        public void onClick(WallpaperPickerActivity a) {}
117        public void onSave(WallpaperPickerActivity a) {}
118        public void onDelete(WallpaperPickerActivity a) {}
119        public boolean isSelectable() { return false; }
120        public boolean isNamelessWallpaper() { return false; }
121        public void onIndexUpdated(CharSequence label) {
122            if (isNamelessWallpaper()) {
123                mView.setContentDescription(label);
124            }
125        }
126    }
127
128    public static class PickImageInfo extends WallpaperTileInfo {
129        @Override
130        public void onClick(WallpaperPickerActivity a) {
131            Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
132            intent.setType("image/*");
133            a.startActivityForResultSafely(intent, IMAGE_PICK);
134        }
135    }
136
137    public static class UriWallpaperInfo extends WallpaperTileInfo {
138        private Uri mUri;
139        private boolean mFirstClick = true;
140        private BitmapRegionTileSource.UriBitmapSource mBitmapSource;
141        public UriWallpaperInfo(Uri uri) {
142            mUri = uri;
143        }
144        @Override
145        public void onClick(final WallpaperPickerActivity a) {
146            final Runnable onLoad;
147            if (!mFirstClick) {
148                onLoad = null;
149            } else {
150                mFirstClick = false;
151                a.mSetWallpaperButton.setEnabled(false);
152                onLoad = new Runnable() {
153                    public void run() {
154                        if (mBitmapSource != null &&
155                                mBitmapSource.getLoadingState() == BitmapSource.State.LOADED) {
156                            a.selectTile(mView);
157                            a.mSetWallpaperButton.setEnabled(true);
158                        } else {
159                            ViewGroup parent = (ViewGroup) mView.getParent();
160                            if (parent != null) {
161                                parent.removeView(mView);
162                                Toast.makeText(a,
163                                        a.getString(R.string.image_load_fail),
164                                        Toast.LENGTH_SHORT).show();
165                            }
166                        }
167                    }
168                };
169            }
170            mBitmapSource = new BitmapRegionTileSource.UriBitmapSource(
171                    a, mUri, BitmapRegionTileSource.MAX_PREVIEW_SIZE);
172            a.setCropViewTileSource(mBitmapSource, true, false, onLoad);
173        }
174        @Override
175        public void onSave(final WallpaperPickerActivity a) {
176            boolean finishActivityWhenDone = true;
177            OnBitmapCroppedHandler h = new OnBitmapCroppedHandler() {
178                public void onBitmapCropped(byte[] imageBytes) {
179                    Point thumbSize = getDefaultThumbnailSize(a.getResources());
180                    // rotation is set to 0 since imageBytes has already been correctly rotated
181                    Bitmap thumb = createThumbnail(
182                            thumbSize, null, null, imageBytes, null, 0, 0, true);
183                    a.getSavedImages().writeImage(thumb, imageBytes);
184                }
185            };
186            a.cropImageAndSetWallpaper(mUri, h, finishActivityWhenDone);
187        }
188        @Override
189        public boolean isSelectable() {
190            return true;
191        }
192        @Override
193        public boolean isNamelessWallpaper() {
194            return true;
195        }
196    }
197
198    public static class FileWallpaperInfo extends WallpaperTileInfo {
199        private File mFile;
200
201        public FileWallpaperInfo(File target, Drawable thumb) {
202            mFile = target;
203            mThumb = thumb;
204        }
205        @Override
206        public void onClick(WallpaperPickerActivity a) {
207            BitmapRegionTileSource.UriBitmapSource bitmapSource =
208                    new BitmapRegionTileSource.UriBitmapSource(a, Uri.fromFile(mFile), 1024);
209            a.setCropViewTileSource(bitmapSource, false, true, null);
210        }
211        @Override
212        public void onSave(WallpaperPickerActivity a) {
213            a.setWallpaper(Uri.fromFile(mFile), true);
214        }
215        @Override
216        public boolean isSelectable() {
217            return true;
218        }
219        @Override
220        public boolean isNamelessWallpaper() {
221            return true;
222        }
223    }
224
225    public static class ResourceWallpaperInfo extends WallpaperTileInfo {
226        private Resources mResources;
227        private int mResId;
228
229        public ResourceWallpaperInfo(Resources res, int resId, Drawable thumb) {
230            mResources = res;
231            mResId = resId;
232            mThumb = thumb;
233        }
234        @Override
235        public void onClick(WallpaperPickerActivity a) {
236            BitmapRegionTileSource.ResourceBitmapSource bitmapSource =
237                    new BitmapRegionTileSource.ResourceBitmapSource(
238                            mResources, mResId, BitmapRegionTileSource.MAX_PREVIEW_SIZE);
239            bitmapSource.loadInBackground();
240            BitmapRegionTileSource source = new BitmapRegionTileSource(a, bitmapSource);
241            CropView v = a.getCropView();
242            v.setTileSource(source, null);
243            Point wallpaperSize = WallpaperCropActivity.getDefaultWallpaperSize(
244                    a.getResources(), a.getWindowManager());
245            RectF crop = WallpaperCropActivity.getMaxCropRect(
246                    source.getImageWidth(), source.getImageHeight(),
247                    wallpaperSize.x, wallpaperSize.y, false);
248            v.setScale(wallpaperSize.x / crop.width());
249            v.setTouchEnabled(false);
250            a.setSystemWallpaperVisiblity(false);
251        }
252        @Override
253        public void onSave(WallpaperPickerActivity a) {
254            boolean finishActivityWhenDone = true;
255            a.cropImageAndSetWallpaper(mResources, mResId, finishActivityWhenDone);
256        }
257        @Override
258        public boolean isSelectable() {
259            return true;
260        }
261        @Override
262        public boolean isNamelessWallpaper() {
263            return true;
264        }
265    }
266
267    @TargetApi(Build.VERSION_CODES.KITKAT)
268    public static class DefaultWallpaperInfo extends WallpaperTileInfo {
269        public DefaultWallpaperInfo(Drawable thumb) {
270            mThumb = thumb;
271        }
272        @Override
273        public void onClick(WallpaperPickerActivity a) {
274            CropView c = a.getCropView();
275
276            Drawable defaultWallpaper = WallpaperManager.getInstance(a).getBuiltInDrawable(
277                    c.getWidth(), c.getHeight(), false, 0.5f, 0.5f);
278
279            if (defaultWallpaper == null) {
280                Log.w(TAG, "Null default wallpaper encountered.");
281                c.setTileSource(null, null);
282                return;
283            }
284
285            c.setTileSource(
286                    new DrawableTileSource(a, defaultWallpaper, DrawableTileSource.MAX_PREVIEW_SIZE), null);
287            c.setScale(1f);
288            c.setTouchEnabled(false);
289            a.setSystemWallpaperVisiblity(false);
290        }
291        @Override
292        public void onSave(WallpaperPickerActivity a) {
293            try {
294                WallpaperManager.getInstance(a).clear();
295                a.setResult(RESULT_OK);
296            } catch (IOException e) {
297                Log.w("Setting wallpaper to default threw exception", e);
298            }
299            a.finish();
300        }
301        @Override
302        public boolean isSelectable() {
303            return true;
304        }
305        @Override
306        public boolean isNamelessWallpaper() {
307            return true;
308        }
309    }
310
311    public void setWallpaperStripYOffset(float offset) {
312        mWallpaperStrip.setPadding(0, 0, 0, (int) offset);
313    }
314
315    /**
316     * shows the system wallpaper behind the window and hides the {@link
317     * #mCropView} if visible
318     * @param visible should the system wallpaper be shown
319     */
320    protected void setSystemWallpaperVisiblity(final boolean visible) {
321        // hide our own wallpaper preview if necessary
322        if(!visible) {
323            mCropView.setVisibility(View.VISIBLE);
324        } else {
325            changeWallpaperFlags(visible);
326        }
327        // the change of the flag must be delayed in order to avoid flickering,
328        // a simple post / double post does not suffice here
329        mCropView.postDelayed(new Runnable() {
330            @Override
331            public void run() {
332                if(!visible) {
333                    changeWallpaperFlags(visible);
334                } else {
335                    mCropView.setVisibility(View.INVISIBLE);
336                }
337            }
338        }, FLAG_POST_DELAY_MILLIS);
339    }
340
341    private void changeWallpaperFlags(boolean visible) {
342        int desiredWallpaperFlag = visible ? WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER : 0;
343        int currentWallpaperFlag = getWindow().getAttributes().flags
344                & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
345        if (desiredWallpaperFlag != currentWallpaperFlag) {
346            getWindow().setFlags(desiredWallpaperFlag,
347                    WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER);
348        }
349    }
350
351    @Override
352    public void setCropViewTileSource(BitmapSource bitmapSource,
353                                      boolean touchEnabled,
354                                      boolean moveToLeft,
355                                      final Runnable postExecute) {
356        // we also want to show our own wallpaper instead of the one in the background
357        Runnable showPostExecuteRunnable = new Runnable() {
358            @Override
359            public void run() {
360                if(postExecute != null) {
361                    postExecute.run();
362                }
363                setSystemWallpaperVisiblity(false);
364            }
365        };
366        super.setCropViewTileSource(bitmapSource,
367                touchEnabled,
368                moveToLeft,
369                showPostExecuteRunnable);
370    }
371
372    // called by onCreate; this is subclassed to overwrite WallpaperCropActivity
373    protected void init() {
374        setContentView(R.layout.wallpaper_picker);
375
376        mCropView = (CropView) findViewById(R.id.cropView);
377        mCropView.setVisibility(View.INVISIBLE);
378
379        mWallpaperStrip = findViewById(R.id.wallpaper_strip);
380        mCropView.setTouchCallback(new CropView.TouchCallback() {
381            ViewPropertyAnimator mAnim;
382            @Override
383            public void onTouchDown() {
384                if (mAnim != null) {
385                    mAnim.cancel();
386                }
387                if (mWallpaperStrip.getAlpha() == 1f) {
388                    mIgnoreNextTap = true;
389                }
390                mAnim = mWallpaperStrip.animate();
391                mAnim.alpha(0f)
392                    .setDuration(150)
393                    .withEndAction(new Runnable() {
394                        public void run() {
395                            mWallpaperStrip.setVisibility(View.INVISIBLE);
396                        }
397                    });
398                mAnim.setInterpolator(new AccelerateInterpolator(0.75f));
399                mAnim.start();
400            }
401            @Override
402            public void onTouchUp() {
403                mIgnoreNextTap = false;
404            }
405            @Override
406            public void onTap() {
407                boolean ignoreTap = mIgnoreNextTap;
408                mIgnoreNextTap = false;
409                if (!ignoreTap) {
410                    if (mAnim != null) {
411                        mAnim.cancel();
412                    }
413                    mWallpaperStrip.setVisibility(View.VISIBLE);
414                    mAnim = mWallpaperStrip.animate();
415                    mAnim.alpha(1f)
416                         .setDuration(150)
417                         .setInterpolator(new DecelerateInterpolator(0.75f));
418                    mAnim.start();
419                }
420            }
421        });
422
423        mThumbnailOnClickListener = new OnClickListener() {
424            public void onClick(View v) {
425                if (mActionMode != null) {
426                    // When CAB is up, clicking toggles the item instead
427                    if (v.isLongClickable()) {
428                        mLongClickListener.onLongClick(v);
429                    }
430                    return;
431                }
432                mSetWallpaperButton.setEnabled(true);
433                WallpaperTileInfo info = (WallpaperTileInfo) v.getTag();
434                if (info.isSelectable() && v.getVisibility() == View.VISIBLE) {
435                    selectTile(v);
436                }
437                info.onClick(WallpaperPickerActivity.this);
438            }
439        };
440        mLongClickListener = new View.OnLongClickListener() {
441            // Called when the user long-clicks on someView
442            public boolean onLongClick(View view) {
443                CheckableFrameLayout c = (CheckableFrameLayout) view;
444                c.toggle();
445
446                if (mActionMode != null) {
447                    mActionMode.invalidate();
448                } else {
449                    // Start the CAB using the ActionMode.Callback defined below
450                    mActionMode = startActionMode(mActionModeCallback);
451                    int childCount = mWallpapersView.getChildCount();
452                    for (int i = 0; i < childCount; i++) {
453                        mWallpapersView.getChildAt(i).setSelected(false);
454                    }
455                }
456                return true;
457            }
458        };
459
460        // Populate the built-in wallpapers
461        ArrayList<WallpaperTileInfo> wallpapers = findBundledWallpapers();
462        mWallpapersView = (LinearLayout) findViewById(R.id.wallpaper_list);
463        SimpleWallpapersAdapter ia = new SimpleWallpapersAdapter(this, wallpapers);
464        populateWallpapersFromAdapter(mWallpapersView, ia, false);
465
466        // Populate the saved wallpapers
467        mSavedImages = new SavedWallpaperImages(this);
468        mSavedImages.loadThumbnailsAndImageIdList();
469        populateWallpapersFromAdapter(mWallpapersView, mSavedImages, true);
470
471        // Populate the live wallpapers
472        final LinearLayout liveWallpapersView =
473                (LinearLayout) findViewById(R.id.live_wallpaper_list);
474        final LiveWallpaperListAdapter a = new LiveWallpaperListAdapter(this);
475        a.registerDataSetObserver(new DataSetObserver() {
476            public void onChanged() {
477                liveWallpapersView.removeAllViews();
478                populateWallpapersFromAdapter(liveWallpapersView, a, false);
479                initializeScrollForRtl();
480                updateTileIndices();
481            }
482        });
483
484        // Populate the third-party wallpaper pickers
485        final LinearLayout thirdPartyWallpapersView =
486                (LinearLayout) findViewById(R.id.third_party_wallpaper_list);
487        final ThirdPartyWallpaperPickerListAdapter ta =
488                new ThirdPartyWallpaperPickerListAdapter(this);
489        populateWallpapersFromAdapter(thirdPartyWallpapersView, ta, false);
490
491        // Add a tile for the Gallery
492        LinearLayout masterWallpaperList = (LinearLayout) findViewById(R.id.master_wallpaper_list);
493        FrameLayout pickImageTile = (FrameLayout) getLayoutInflater().
494                inflate(R.layout.wallpaper_picker_image_picker_item, masterWallpaperList, false);
495        setWallpaperItemPaddingToZero(pickImageTile);
496        masterWallpaperList.addView(pickImageTile, 0);
497
498        // Make its background the last photo taken on external storage
499        Bitmap lastPhoto = getThumbnailOfLastPhoto();
500        if (lastPhoto != null) {
501            ImageView galleryThumbnailBg =
502                    (ImageView) pickImageTile.findViewById(R.id.wallpaper_image);
503            galleryThumbnailBg.setImageBitmap(getThumbnailOfLastPhoto());
504            int colorOverlay = getResources().getColor(R.color.wallpaper_picker_translucent_gray);
505            galleryThumbnailBg.setColorFilter(colorOverlay, PorterDuff.Mode.SRC_ATOP);
506
507        }
508
509        PickImageInfo pickImageInfo = new PickImageInfo();
510        pickImageTile.setTag(pickImageInfo);
511        pickImageInfo.setView(pickImageTile);
512        pickImageTile.setOnClickListener(mThumbnailOnClickListener);
513
514        // Select the first item; wait for a layout pass so that we initialize the dimensions of
515        // cropView or the defaultWallpaperView first
516        mCropView.addOnLayoutChangeListener(new OnLayoutChangeListener() {
517            @Override
518            public void onLayoutChange(View v, int left, int top, int right, int bottom,
519                    int oldLeft, int oldTop, int oldRight, int oldBottom) {
520                if ((right - left) > 0 && (bottom - top) > 0) {
521                    if (mSelectedIndex >= 0 && mSelectedIndex < mWallpapersView.getChildCount()) {
522                        mThumbnailOnClickListener.onClick(
523                                mWallpapersView.getChildAt(mSelectedIndex));
524                        setSystemWallpaperVisiblity(false);
525                    }
526                    v.removeOnLayoutChangeListener(this);
527                }
528            }
529        });
530
531        updateTileIndices();
532
533        // Update the scroll for RTL
534        initializeScrollForRtl();
535
536        // Create smooth layout transitions for when items are deleted
537        final LayoutTransition transitioner = new LayoutTransition();
538        transitioner.setDuration(200);
539        transitioner.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, 0);
540        transitioner.setAnimator(LayoutTransition.DISAPPEARING, null);
541        mWallpapersView.setLayoutTransition(transitioner);
542
543        // Action bar
544        // Show the custom action bar view
545        final ActionBar actionBar = getActionBar();
546        actionBar.setCustomView(R.layout.actionbar_set_wallpaper);
547        actionBar.getCustomView().setOnClickListener(
548                new View.OnClickListener() {
549                    @Override
550                    public void onClick(View v) {
551                        if (mSelectedTile != null) {
552                            WallpaperTileInfo info = (WallpaperTileInfo) mSelectedTile.getTag();
553                            info.onSave(WallpaperPickerActivity.this);
554                        } else {
555                            // no tile was selected, so we just finish the activity and go back
556                            setResult(Activity.RESULT_OK);
557                            finish();
558                        }
559                    }
560                });
561        mSetWallpaperButton = findViewById(R.id.set_wallpaper_button);
562
563        // CAB for deleting items
564        mActionModeCallback = new ActionMode.Callback() {
565            // Called when the action mode is created; startActionMode() was called
566            @Override
567            public boolean onCreateActionMode(ActionMode mode, Menu menu) {
568                // Inflate a menu resource providing context menu items
569                MenuInflater inflater = mode.getMenuInflater();
570                inflater.inflate(R.menu.cab_delete_wallpapers, menu);
571                return true;
572            }
573
574            private int numCheckedItems() {
575                int childCount = mWallpapersView.getChildCount();
576                int numCheckedItems = 0;
577                for (int i = 0; i < childCount; i++) {
578                    CheckableFrameLayout c = (CheckableFrameLayout) mWallpapersView.getChildAt(i);
579                    if (c.isChecked()) {
580                        numCheckedItems++;
581                    }
582                }
583                return numCheckedItems;
584            }
585
586            // Called each time the action mode is shown. Always called after onCreateActionMode,
587            // but may be called multiple times if the mode is invalidated.
588            @Override
589            public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
590                int numCheckedItems = numCheckedItems();
591                if (numCheckedItems == 0) {
592                    mode.finish();
593                    return true;
594                } else {
595                    mode.setTitle(getResources().getQuantityString(
596                            R.plurals.number_of_items_selected, numCheckedItems, numCheckedItems));
597                    return true;
598                }
599            }
600
601            // Called when the user selects a contextual menu item
602            @Override
603            public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
604                int itemId = item.getItemId();
605                if (itemId == R.id.menu_delete) {
606                    int childCount = mWallpapersView.getChildCount();
607                    ArrayList<View> viewsToRemove = new ArrayList<View>();
608                    boolean selectedTileRemoved = false;
609                    for (int i = 0; i < childCount; i++) {
610                        CheckableFrameLayout c =
611                                (CheckableFrameLayout) mWallpapersView.getChildAt(i);
612                        if (c.isChecked()) {
613                            WallpaperTileInfo info = (WallpaperTileInfo) c.getTag();
614                            info.onDelete(WallpaperPickerActivity.this);
615                            viewsToRemove.add(c);
616                            if (i == mSelectedIndex) {
617                                selectedTileRemoved = true;
618                            }
619                        }
620                    }
621                    for (View v : viewsToRemove) {
622                        mWallpapersView.removeView(v);
623                    }
624                    if (selectedTileRemoved) {
625                        mSelectedIndex = -1;
626                        mSelectedTile = null;
627                        setSystemWallpaperVisiblity(true);
628                    }
629                    updateTileIndices();
630                    mode.finish(); // Action picked, so close the CAB
631                    return true;
632                } else {
633                    return false;
634                }
635            }
636
637            // Called when the user exits the action mode
638            @Override
639            public void onDestroyActionMode(ActionMode mode) {
640                int childCount = mWallpapersView.getChildCount();
641                for (int i = 0; i < childCount; i++) {
642                    CheckableFrameLayout c = (CheckableFrameLayout) mWallpapersView.getChildAt(i);
643                    c.setChecked(false);
644                }
645                if (mSelectedTile != null) {
646                    mSelectedTile.setSelected(true);
647                }
648                mActionMode = null;
649            }
650        };
651    }
652
653    private void selectTile(View v) {
654        if (mSelectedTile != null) {
655            mSelectedTile.setSelected(false);
656            mSelectedTile = null;
657        }
658        mSelectedTile = v;
659        v.setSelected(true);
660        mSelectedIndex = mWallpapersView.indexOfChild(v);
661        // TODO: Remove this once the accessibility framework and
662        // services have better support for selection state.
663        v.announceForAccessibility(
664                getString(R.string.announce_selection, v.getContentDescription()));
665    }
666
667    private void initializeScrollForRtl() {
668        final HorizontalScrollView scroll =
669                (HorizontalScrollView) findViewById(R.id.wallpaper_scroll_container);
670
671        if (scroll.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
672            final ViewTreeObserver observer = scroll.getViewTreeObserver();
673            observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
674                public void onGlobalLayout() {
675                    LinearLayout masterWallpaperList =
676                            (LinearLayout) findViewById(R.id.master_wallpaper_list);
677                    scroll.scrollTo(masterWallpaperList.getWidth(), 0);
678                    scroll.getViewTreeObserver().removeOnGlobalLayoutListener(this);
679                }
680            });
681        }
682    }
683
684    protected Bitmap getThumbnailOfLastPhoto() {
685        Cursor cursor = MediaStore.Images.Media.query(getContentResolver(),
686                MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
687                new String[] { MediaStore.Images.ImageColumns._ID,
688                    MediaStore.Images.ImageColumns.DATE_TAKEN},
689                null, null, MediaStore.Images.ImageColumns.DATE_TAKEN + " DESC LIMIT 1");
690
691        Bitmap thumb = null;
692        if (cursor != null) {
693            if (cursor.moveToNext()) {
694                int id = cursor.getInt(0);
695                thumb = MediaStore.Images.Thumbnails.getThumbnail(getContentResolver(),
696                        id, MediaStore.Images.Thumbnails.MINI_KIND, null);
697            }
698            cursor.close();
699        }
700        return thumb;
701    }
702
703    protected void onStop() {
704        super.onStop();
705        mWallpaperStrip = findViewById(R.id.wallpaper_strip);
706        if (mWallpaperStrip.getAlpha() < 1f) {
707            mWallpaperStrip.setAlpha(1f);
708            mWallpaperStrip.setVisibility(View.VISIBLE);
709        }
710    }
711
712    protected void onSaveInstanceState(Bundle outState) {
713        outState.putParcelableArrayList(TEMP_WALLPAPER_TILES, mTempWallpaperTiles);
714        outState.putInt(SELECTED_INDEX, mSelectedIndex);
715    }
716
717    protected void onRestoreInstanceState(Bundle savedInstanceState) {
718        ArrayList<Uri> uris = savedInstanceState.getParcelableArrayList(TEMP_WALLPAPER_TILES);
719        for (Uri uri : uris) {
720            addTemporaryWallpaperTile(uri, true);
721        }
722        mSelectedIndex = savedInstanceState.getInt(SELECTED_INDEX, -1);
723    }
724
725    private void populateWallpapersFromAdapter(ViewGroup parent, BaseAdapter adapter,
726            boolean addLongPressHandler) {
727        for (int i = 0; i < adapter.getCount(); i++) {
728            FrameLayout thumbnail = (FrameLayout) adapter.getView(i, null, parent);
729            parent.addView(thumbnail, i);
730            WallpaperTileInfo info = (WallpaperTileInfo) adapter.getItem(i);
731            thumbnail.setTag(info);
732            info.setView(thumbnail);
733            if (addLongPressHandler) {
734                addLongPressHandler(thumbnail);
735            }
736            thumbnail.setOnClickListener(mThumbnailOnClickListener);
737        }
738    }
739
740    private void updateTileIndices() {
741        LinearLayout masterWallpaperList = (LinearLayout) findViewById(R.id.master_wallpaper_list);
742        final int childCount = masterWallpaperList.getChildCount();
743        final Resources res = getResources();
744
745        // Do two passes; the first pass gets the total number of tiles
746        int numTiles = 0;
747        for (int passNum = 0; passNum < 2; passNum++) {
748            int tileIndex = 0;
749            for (int i = 0; i < childCount; i++) {
750                View child = masterWallpaperList.getChildAt(i);
751                LinearLayout subList;
752
753                int subListStart;
754                int subListEnd;
755                if (child.getTag() instanceof WallpaperTileInfo) {
756                    subList = masterWallpaperList;
757                    subListStart = i;
758                    subListEnd = i + 1;
759                } else { // if (child instanceof LinearLayout) {
760                    subList = (LinearLayout) child;
761                    subListStart = 0;
762                    subListEnd = subList.getChildCount();
763                }
764
765                for (int j = subListStart; j < subListEnd; j++) {
766                    WallpaperTileInfo info = (WallpaperTileInfo) subList.getChildAt(j).getTag();
767                    if (info.isNamelessWallpaper()) {
768                        if (passNum == 0) {
769                            numTiles++;
770                        } else {
771                            CharSequence label = res.getString(
772                                    R.string.wallpaper_accessibility_name, ++tileIndex, numTiles);
773                            info.onIndexUpdated(label);
774                        }
775                    }
776                }
777            }
778        }
779    }
780
781    private static Point getDefaultThumbnailSize(Resources res) {
782        return new Point(res.getDimensionPixelSize(R.dimen.wallpaperThumbnailWidth),
783                res.getDimensionPixelSize(R.dimen.wallpaperThumbnailHeight));
784
785    }
786
787    private static Bitmap createThumbnail(Point size, Context context, Uri uri, byte[] imageBytes,
788            Resources res, int resId, int rotation, boolean leftAligned) {
789        int width = size.x;
790        int height = size.y;
791
792        BitmapCropTask cropTask;
793        if (uri != null) {
794            cropTask = new BitmapCropTask(
795                    context, uri, null, rotation, width, height, false, true, null);
796        } else if (imageBytes != null) {
797            cropTask = new BitmapCropTask(
798                    imageBytes, null, rotation, width, height, false, true, null);
799        }  else {
800            cropTask = new BitmapCropTask(
801                    context, res, resId, null, rotation, width, height, false, true, null);
802        }
803        Point bounds = cropTask.getImageBounds();
804        if (bounds == null || bounds.x == 0 || bounds.y == 0) {
805            return null;
806        }
807
808        Matrix rotateMatrix = new Matrix();
809        rotateMatrix.setRotate(rotation);
810        float[] rotatedBounds = new float[] { bounds.x, bounds.y };
811        rotateMatrix.mapPoints(rotatedBounds);
812        rotatedBounds[0] = Math.abs(rotatedBounds[0]);
813        rotatedBounds[1] = Math.abs(rotatedBounds[1]);
814
815        RectF cropRect = WallpaperCropActivity.getMaxCropRect(
816                (int) rotatedBounds[0], (int) rotatedBounds[1], width, height, leftAligned);
817        cropTask.setCropBounds(cropRect);
818
819        if (cropTask.cropBitmap()) {
820            return cropTask.getCroppedBitmap();
821        } else {
822            return null;
823        }
824    }
825
826    private void addTemporaryWallpaperTile(final Uri uri, boolean fromRestore) {
827        mTempWallpaperTiles.add(uri);
828        // Add a tile for the image picked from Gallery
829        final FrameLayout pickedImageThumbnail = (FrameLayout) getLayoutInflater().
830                inflate(R.layout.wallpaper_picker_item, mWallpapersView, false);
831        pickedImageThumbnail.setVisibility(View.GONE);
832        setWallpaperItemPaddingToZero(pickedImageThumbnail);
833        mWallpapersView.addView(pickedImageThumbnail, 0);
834
835        // Load the thumbnail
836        final ImageView image = (ImageView) pickedImageThumbnail.findViewById(R.id.wallpaper_image);
837        final Point defaultSize = getDefaultThumbnailSize(this.getResources());
838        final Context context = this;
839        new AsyncTask<Void, Bitmap, Bitmap>() {
840            protected Bitmap doInBackground(Void...args) {
841                try {
842                    int rotation = WallpaperCropActivity.getRotationFromExif(context, uri);
843                    return createThumbnail(defaultSize, context, uri, null, null, 0, rotation, false);
844                } catch (SecurityException securityException) {
845                    if (isDestroyed()) {
846                        // Temporarily granted permissions are revoked when the activity
847                        // finishes, potentially resulting in a SecurityException here.
848                        // Even though {@link #isDestroyed} might also return true in different
849                        // situations where the configuration changes, we are fine with
850                        // catching these cases here as well.
851                        cancel(false);
852                    } else {
853                        // otherwise it had a different cause and we throw it further
854                        throw securityException;
855                    }
856                    return null;
857                }
858            }
859            protected void onPostExecute(Bitmap thumb) {
860                if (!isCancelled() && thumb != null) {
861                    image.setImageBitmap(thumb);
862                    Drawable thumbDrawable = image.getDrawable();
863                    thumbDrawable.setDither(true);
864                    pickedImageThumbnail.setVisibility(View.VISIBLE);
865                } else {
866                    Log.e(TAG, "Error loading thumbnail for uri=" + uri);
867                }
868            }
869        }.execute();
870
871        UriWallpaperInfo info = new UriWallpaperInfo(uri);
872        pickedImageThumbnail.setTag(info);
873        info.setView(pickedImageThumbnail);
874        addLongPressHandler(pickedImageThumbnail);
875        updateTileIndices();
876        pickedImageThumbnail.setOnClickListener(mThumbnailOnClickListener);
877        if (!fromRestore) {
878            mThumbnailOnClickListener.onClick(pickedImageThumbnail);
879        }
880    }
881
882    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
883        if (requestCode == IMAGE_PICK && resultCode == RESULT_OK) {
884            if (data != null && data.getData() != null) {
885                Uri uri = data.getData();
886                addTemporaryWallpaperTile(uri, false);
887            }
888        } else if (requestCode == PICK_WALLPAPER_THIRD_PARTY_ACTIVITY) {
889            setResult(RESULT_OK);
890            finish();
891        } else if (requestCode == PICK_LIVE_WALLPAPER) {
892            WallpaperManager wm = WallpaperManager.getInstance(this);
893            final WallpaperInfo oldLiveWallpaper = mLiveWallpaperInfoOnPickerLaunch;
894            final WallpaperInfo clickedWallpaper = mLastClickedLiveWallpaperInfo;
895            WallpaperInfo newLiveWallpaper = wm.getWallpaperInfo();
896            // Try to figure out if a live wallpaper was set;
897            if (newLiveWallpaper != null &&
898                    (oldLiveWallpaper == null
899                            || !oldLiveWallpaper.getComponent()
900                                    .equals(newLiveWallpaper.getComponent())
901                            || clickedWallpaper.getComponent()
902                                    .equals(oldLiveWallpaper.getComponent()))) {
903                // Return if a live wallpaper was set
904                setResult(RESULT_OK);
905                finish();
906            }
907        }
908    }
909
910    static void setWallpaperItemPaddingToZero(FrameLayout frameLayout) {
911        frameLayout.setPadding(0, 0, 0, 0);
912        frameLayout.setForeground(new ZeroPaddingDrawable(frameLayout.getForeground()));
913    }
914
915    private void addLongPressHandler(View v) {
916        v.setOnLongClickListener(mLongClickListener);
917    }
918
919    private ArrayList<WallpaperTileInfo> findBundledWallpapers() {
920        final PackageManager pm = getPackageManager();
921        final ArrayList<WallpaperTileInfo> bundled = new ArrayList<WallpaperTileInfo>(24);
922
923        Partner partner = Partner.get(pm);
924        if (partner != null) {
925            final Resources partnerRes = partner.getResources();
926            final int resId = partnerRes.getIdentifier(Partner.RES_WALLPAPERS, "array",
927                    partner.getPackageName());
928            if (resId != 0) {
929                addWallpapers(bundled, partnerRes, partner.getPackageName(), resId);
930            }
931
932            // Add system wallpapers
933            File systemDir = partner.getWallpaperDirectory();
934            if (systemDir != null && systemDir.isDirectory()) {
935                for (File file : systemDir.listFiles()) {
936                    if (!file.isFile()) {
937                        continue;
938                    }
939                    String name = file.getName();
940                    int dotPos = name.lastIndexOf('.');
941                    String extension = "";
942                    if (dotPos >= -1) {
943                        extension = name.substring(dotPos);
944                        name = name.substring(0, dotPos);
945                    }
946
947                    if (name.endsWith("_small")) {
948                        // it is a thumbnail
949                        continue;
950                    }
951
952                    File thumbnail = new File(systemDir, name + "_small" + extension);
953                    Bitmap thumb = BitmapFactory.decodeFile(thumbnail.getAbsolutePath());
954                    if (thumb != null) {
955                        bundled.add(new FileWallpaperInfo(file, new BitmapDrawable(thumb)));
956                    }
957                }
958            }
959        }
960
961        Pair<ApplicationInfo, Integer> r = getWallpaperArrayResourceId();
962        if (r != null) {
963            try {
964                Resources wallpaperRes = getPackageManager().getResourcesForApplication(r.first);
965                addWallpapers(bundled, wallpaperRes, r.first.packageName, r.second);
966            } catch (PackageManager.NameNotFoundException e) {
967            }
968        }
969
970        if (partner == null || !partner.hideDefaultWallpaper()) {
971            // Add an entry for the default wallpaper (stored in system resources)
972            WallpaperTileInfo defaultWallpaperInfo =
973                    (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT)
974                    ? getPreKKDefaultWallpaperInfo()
975                    : getDefaultWallpaper();
976            if (defaultWallpaperInfo != null) {
977                bundled.add(0, defaultWallpaperInfo);
978            }
979        }
980        return bundled;
981    }
982
983    private boolean writeImageToFileAsJpeg(File f, Bitmap b) {
984        try {
985            f.createNewFile();
986            FileOutputStream thumbFileStream =
987                    openFileOutput(f.getName(), Context.MODE_PRIVATE);
988            b.compress(Bitmap.CompressFormat.JPEG, 95, thumbFileStream);
989            thumbFileStream.close();
990            return true;
991        } catch (IOException e) {
992            Log.e(TAG, "Error while writing bitmap to file " + e);
993            f.delete();
994        }
995        return false;
996    }
997
998    private File getDefaultThumbFile() {
999        return new File(getFilesDir(), Build.VERSION.SDK_INT
1000                + "_" + LauncherFiles.DEFAULT_WALLPAPER_THUMBNAIL);
1001    }
1002
1003    private boolean saveDefaultWallpaperThumb(Bitmap b) {
1004        // Delete old thumbnails.
1005        new File(getFilesDir(), LauncherFiles.DEFAULT_WALLPAPER_THUMBNAIL_OLD).delete();
1006        new File(getFilesDir(), LauncherFiles.DEFAULT_WALLPAPER_THUMBNAIL).delete();
1007
1008        for (int i = Build.VERSION_CODES.JELLY_BEAN; i < Build.VERSION.SDK_INT; i++) {
1009            new File(getFilesDir(), i + "_" + LauncherFiles.DEFAULT_WALLPAPER_THUMBNAIL).delete();
1010        }
1011        return writeImageToFileAsJpeg(getDefaultThumbFile(), b);
1012    }
1013
1014    private ResourceWallpaperInfo getPreKKDefaultWallpaperInfo() {
1015        Resources sysRes = Resources.getSystem();
1016        int resId = sysRes.getIdentifier("default_wallpaper", "drawable", "android");
1017
1018        File defaultThumbFile = getDefaultThumbFile();
1019        Bitmap thumb = null;
1020        boolean defaultWallpaperExists = false;
1021        if (defaultThumbFile.exists()) {
1022            thumb = BitmapFactory.decodeFile(defaultThumbFile.getAbsolutePath());
1023            defaultWallpaperExists = true;
1024        } else {
1025            Resources res = getResources();
1026            Point defaultThumbSize = getDefaultThumbnailSize(res);
1027            int rotation = WallpaperCropActivity.getRotationFromExif(res, resId);
1028            thumb = createThumbnail(
1029                    defaultThumbSize, this, null, null, sysRes, resId, rotation, false);
1030            if (thumb != null) {
1031                defaultWallpaperExists = saveDefaultWallpaperThumb(thumb);
1032            }
1033        }
1034        if (defaultWallpaperExists) {
1035            return new ResourceWallpaperInfo(sysRes, resId, new BitmapDrawable(thumb));
1036        }
1037        return null;
1038    }
1039
1040    @TargetApi(Build.VERSION_CODES.KITKAT)
1041    private DefaultWallpaperInfo getDefaultWallpaper() {
1042        File defaultThumbFile = getDefaultThumbFile();
1043        Bitmap thumb = null;
1044        boolean defaultWallpaperExists = false;
1045        if (defaultThumbFile.exists()) {
1046            thumb = BitmapFactory.decodeFile(defaultThumbFile.getAbsolutePath());
1047            defaultWallpaperExists = true;
1048        } else {
1049            Resources res = getResources();
1050            Point defaultThumbSize = getDefaultThumbnailSize(res);
1051            Drawable wallpaperDrawable = WallpaperManager.getInstance(this).getBuiltInDrawable(
1052                    defaultThumbSize.x, defaultThumbSize.y, true, 0.5f, 0.5f);
1053            if (wallpaperDrawable != null) {
1054                thumb = Bitmap.createBitmap(
1055                        defaultThumbSize.x, defaultThumbSize.y, Bitmap.Config.ARGB_8888);
1056                Canvas c = new Canvas(thumb);
1057                wallpaperDrawable.setBounds(0, 0, defaultThumbSize.x, defaultThumbSize.y);
1058                wallpaperDrawable.draw(c);
1059                c.setBitmap(null);
1060            }
1061            if (thumb != null) {
1062                defaultWallpaperExists = saveDefaultWallpaperThumb(thumb);
1063            }
1064        }
1065        if (defaultWallpaperExists) {
1066            return new DefaultWallpaperInfo(new BitmapDrawable(thumb));
1067        }
1068        return null;
1069    }
1070
1071    public Pair<ApplicationInfo, Integer> getWallpaperArrayResourceId() {
1072        // Context.getPackageName() may return the "original" package name,
1073        // com.android.launcher3; Resources needs the real package name,
1074        // com.android.launcher3. So we ask Resources for what it thinks the
1075        // package name should be.
1076        final String packageName = getResources().getResourcePackageName(R.array.wallpapers);
1077        try {
1078            ApplicationInfo info = getPackageManager().getApplicationInfo(packageName, 0);
1079            return new Pair<ApplicationInfo, Integer>(info, R.array.wallpapers);
1080        } catch (PackageManager.NameNotFoundException e) {
1081            return null;
1082        }
1083    }
1084
1085    private void addWallpapers(ArrayList<WallpaperTileInfo> known, Resources res,
1086            String packageName, int listResId) {
1087        final String[] extras = res.getStringArray(listResId);
1088        for (String extra : extras) {
1089            int resId = res.getIdentifier(extra, "drawable", packageName);
1090            if (resId != 0) {
1091                final int thumbRes = res.getIdentifier(extra + "_small", "drawable", packageName);
1092
1093                if (thumbRes != 0) {
1094                    ResourceWallpaperInfo wallpaperInfo =
1095                            new ResourceWallpaperInfo(res, resId, res.getDrawable(thumbRes));
1096                    known.add(wallpaperInfo);
1097                    // Log.d(TAG, "add: [" + packageName + "]: " + extra + " (" + res + ")");
1098                }
1099            } else {
1100                Log.e(TAG, "Couldn't find wallpaper " + extra);
1101            }
1102        }
1103    }
1104
1105    public CropView getCropView() {
1106        return mCropView;
1107    }
1108
1109    public SavedWallpaperImages getSavedImages() {
1110        return mSavedImages;
1111    }
1112
1113    public void onLiveWallpaperPickerLaunch(WallpaperInfo info) {
1114        mLastClickedLiveWallpaperInfo = info;
1115        mLiveWallpaperInfoOnPickerLaunch = WallpaperManager.getInstance(this).getWallpaperInfo();
1116    }
1117
1118    static class ZeroPaddingDrawable extends LevelListDrawable {
1119        public ZeroPaddingDrawable(Drawable d) {
1120            super();
1121            addLevel(0, 0, d);
1122            setLevel(0);
1123        }
1124
1125        @Override
1126        public boolean getPadding(Rect padding) {
1127            padding.set(0, 0, 0, 0);
1128            return true;
1129        }
1130    }
1131
1132    private static class SimpleWallpapersAdapter extends ArrayAdapter<WallpaperTileInfo> {
1133        private final LayoutInflater mLayoutInflater;
1134
1135        SimpleWallpapersAdapter(Activity activity, ArrayList<WallpaperTileInfo> wallpapers) {
1136            super(activity, R.layout.wallpaper_picker_item, wallpapers);
1137            mLayoutInflater = activity.getLayoutInflater();
1138        }
1139
1140        public View getView(int position, View convertView, ViewGroup parent) {
1141            Drawable thumb = getItem(position).mThumb;
1142            if (thumb == null) {
1143                Log.e(TAG, "Error decoding thumbnail for wallpaper #" + position);
1144            }
1145            return createImageTileView(mLayoutInflater, convertView, parent, thumb);
1146        }
1147    }
1148
1149    public static View createImageTileView(LayoutInflater layoutInflater,
1150            View convertView, ViewGroup parent, Drawable thumb) {
1151        View view;
1152
1153        if (convertView == null) {
1154            view = layoutInflater.inflate(R.layout.wallpaper_picker_item, parent, false);
1155        } else {
1156            view = convertView;
1157        }
1158
1159        setWallpaperItemPaddingToZero((FrameLayout) view);
1160
1161        ImageView image = (ImageView) view.findViewById(R.id.wallpaper_image);
1162
1163        if (thumb != null) {
1164            image.setImageDrawable(thumb);
1165            thumb.setDither(true);
1166        }
1167
1168        return view;
1169    }
1170
1171    // In Launcher3, we override this with a method that catches exceptions
1172    // from starting activities; didn't want to copy and paste code into here
1173    public void startActivityForResultSafely(Intent intent, int requestCode) {
1174        startActivityForResult(intent, requestCode);
1175    }
1176}
1177