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