1 /*
2  * Copyright (C) 2013 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.wallpaperpicker;
18 
19 import android.animation.LayoutTransition;
20 import android.app.ActionBar;
21 import android.app.Activity;
22 import android.content.Intent;
23 import android.content.pm.ApplicationInfo;
24 import android.content.pm.PackageManager;
25 import android.content.res.Resources;
26 import android.net.Uri;
27 import android.os.Bundle;
28 import android.util.Log;
29 import android.util.Pair;
30 import android.view.ActionMode;
31 import android.view.Menu;
32 import android.view.MenuInflater;
33 import android.view.MenuItem;
34 import android.view.View;
35 import android.view.View.OnClickListener;
36 import android.view.View.OnLayoutChangeListener;
37 import android.view.View.OnLongClickListener;
38 import android.view.ViewGroup;
39 import android.view.ViewTreeObserver;
40 import android.view.ViewTreeObserver.OnGlobalLayoutListener;
41 import android.view.WindowManager;
42 import android.widget.HorizontalScrollView;
43 import android.widget.LinearLayout;
44 
45 import com.android.wallpaperpicker.tileinfo.DefaultWallpaperInfo;
46 import com.android.wallpaperpicker.tileinfo.LiveWallpaperInfo;
47 import com.android.wallpaperpicker.tileinfo.PickImageInfo;
48 import com.android.wallpaperpicker.tileinfo.ResourceWallpaperInfo;
49 import com.android.wallpaperpicker.tileinfo.ThirdPartyWallpaperInfo;
50 import com.android.wallpaperpicker.tileinfo.UriWallpaperInfo;
51 import com.android.wallpaperpicker.tileinfo.WallpaperTileInfo;
52 
53 import java.util.ArrayList;
54 import java.util.List;
55 
56 public class WallpaperPickerActivity extends WallpaperCropActivity
57         implements OnClickListener, OnLongClickListener, ActionMode.Callback {
58     static final String TAG = "WallpaperPickerActivity";
59 
60     public static final int IMAGE_PICK = 5;
61     public static final int PICK_WALLPAPER_THIRD_PARTY_ACTIVITY = 6;
62     private static final String TEMP_WALLPAPER_TILES = "TEMP_WALLPAPER_TILES";
63     private static final String SELECTED_INDEX = "SELECTED_INDEX";
64     private static final int FLAG_POST_DELAY_MILLIS = 200;
65 
66     private View mSelectedTile;
67 
68     private LinearLayout mWallpapersView;
69     private HorizontalScrollView mWallpaperScrollContainer;
70     private View mWallpaperStrip;
71 
72     private ActionMode mActionMode;
73 
74     ArrayList<Uri> mTempWallpaperTiles = new ArrayList<Uri>();
75     private SavedWallpaperImages mSavedImages;
76     private int mSelectedIndex = -1;
77     private float mWallpaperParallaxOffset;
78 
79     /**
80      * shows the system wallpaper behind the window and hides the {@link #mCropView} if visible
81      * @param visible should the system wallpaper be shown
82      */
setSystemWallpaperVisiblity(final boolean visible)83     protected void setSystemWallpaperVisiblity(final boolean visible) {
84         // hide our own wallpaper preview if necessary
85         if(!visible) {
86             mCropView.setVisibility(View.VISIBLE);
87         } else {
88             changeWallpaperFlags(visible);
89         }
90         // the change of the flag must be delayed in order to avoid flickering,
91         // a simple post / double post does not suffice here
92         mCropView.postDelayed(new Runnable() {
93             @Override
94             public void run() {
95                 if(!visible) {
96                     changeWallpaperFlags(visible);
97                 } else {
98                     mCropView.setVisibility(View.INVISIBLE);
99                 }
100             }
101         }, FLAG_POST_DELAY_MILLIS);
102     }
103 
changeWallpaperFlags(boolean visible)104     private void changeWallpaperFlags(boolean visible) {
105         int desiredWallpaperFlag = visible ? WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER : 0;
106         int currentWallpaperFlag = getWindow().getAttributes().flags
107                 & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
108         if (desiredWallpaperFlag != currentWallpaperFlag) {
109             getWindow().setFlags(desiredWallpaperFlag,
110                     WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER);
111         }
112     }
113 
114     @Override
onLoadRequestComplete(LoadRequest req, boolean success)115     protected void onLoadRequestComplete(LoadRequest req, boolean success) {
116         super.onLoadRequestComplete(req, success);
117         if (success) {
118             setSystemWallpaperVisiblity(false);
119         }
120     }
121 
122     /**
123      * called by onCreate; this is sub-classed to overwrite WallpaperCropActivity
124      */
init()125     protected void init() {
126         setContentView(R.layout.wallpaper_picker);
127 
128         mCropView = (CropView) findViewById(R.id.cropView);
129         mCropView.setVisibility(View.INVISIBLE);
130 
131         mProgressView = findViewById(R.id.loading);
132         mWallpaperScrollContainer = (HorizontalScrollView) findViewById(R.id.wallpaper_scroll_container);
133         mWallpaperStrip = findViewById(R.id.wallpaper_strip);
134         mCropView.setTouchCallback(new ToggleOnTapCallback(mWallpaperStrip));
135 
136         mWallpaperParallaxOffset = getIntent().getFloatExtra(
137                 WallpaperUtils.EXTRA_WALLPAPER_OFFSET, 0);
138 
139         mWallpapersView = (LinearLayout) findViewById(R.id.wallpaper_list);
140         // Populate the saved wallpapers
141         mSavedImages = new SavedWallpaperImages(this);
142         populateWallpapers(mWallpapersView, mSavedImages.loadThumbnailsAndImageIdList(), true);
143 
144         // Populate the built-in wallpapers
145         ArrayList<WallpaperTileInfo> wallpapers = findBundledWallpapers();
146         populateWallpapers(mWallpapersView, wallpapers, false);
147 
148         // Load live wallpapers asynchronously
149         new LiveWallpaperInfo.LoaderTask(this) {
150 
151             @Override
152             protected void onPostExecute(List<LiveWallpaperInfo> result) {
153                 populateWallpapers((LinearLayout) findViewById(R.id.live_wallpaper_list),
154                         result, false);
155                 initializeScrollForRtl();
156                 updateTileIndices();
157             }
158         }.execute();
159 
160         // Populate the third-party wallpaper pickers
161         populateWallpapers((LinearLayout) findViewById(R.id.third_party_wallpaper_list),
162                 ThirdPartyWallpaperInfo.getAll(this), false /* addLongPressHandler */);
163 
164         // Add a tile for the Gallery
165         LinearLayout masterWallpaperList = (LinearLayout) findViewById(R.id.master_wallpaper_list);
166         masterWallpaperList.addView(
167                 createTileView(masterWallpaperList, new PickImageInfo(), false), 0);
168 
169         // Select the first item; wait for a layout pass so that we initialize the dimensions of
170         // cropView or the defaultWallpaperView first
171         mCropView.addOnLayoutChangeListener(new OnLayoutChangeListener() {
172             @Override
173             public void onLayoutChange(View v, int left, int top, int right, int bottom,
174                     int oldLeft, int oldTop, int oldRight, int oldBottom) {
175                 if ((right - left) > 0 && (bottom - top) > 0) {
176                     if (mSelectedIndex >= 0 && mSelectedIndex < mWallpapersView.getChildCount()) {
177                         onClick(mWallpapersView.getChildAt(mSelectedIndex));
178                         setSystemWallpaperVisiblity(false);
179                     }
180                     v.removeOnLayoutChangeListener(this);
181                 }
182             }
183         });
184 
185         updateTileIndices();
186 
187         // Update the scroll for RTL
188         initializeScrollForRtl();
189 
190         // Create smooth layout transitions for when items are deleted
191         final LayoutTransition transitioner = new LayoutTransition();
192         transitioner.setDuration(200);
193         transitioner.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, 0);
194         transitioner.setAnimator(LayoutTransition.DISAPPEARING, null);
195         mWallpapersView.setLayoutTransition(transitioner);
196 
197         // Action bar
198         // Show the custom action bar view
199         final ActionBar actionBar = getActionBar();
200         actionBar.setCustomView(R.layout.actionbar_set_wallpaper);
201         actionBar.getCustomView().setOnClickListener(
202                 new View.OnClickListener() {
203                     @Override
204                     public void onClick(View v) {
205                         // Ensure that a tile is selected and loaded.
206                         if (mSelectedTile != null && mCropView.getTileSource() != null) {
207                             // Prevent user from selecting any new tile.
208                             mWallpaperStrip.setVisibility(View.GONE);
209                             actionBar.hide();
210 
211                             WallpaperTileInfo info = (WallpaperTileInfo) mSelectedTile.getTag();
212                             info.onSave(WallpaperPickerActivity.this);
213                         } else {
214                             // This case shouldn't be possible, since "Set wallpaper" is disabled
215                             // until user clicks on a title.
216                             Log.w(TAG, "\"Set wallpaper\" was clicked when no tile was selected");
217                         }
218                     }
219                 });
220         mSetWallpaperButton = findViewById(R.id.set_wallpaper_button);
221     }
222 
223     /**
224      * Called when a wallpaper tile is clicked
225      */
226     @Override
onClick(View v)227     public void onClick(View v) {
228         if (mActionMode != null) {
229             // When CAB is up, clicking toggles the item instead
230             if (v.isLongClickable()) {
231                 onLongClick(v);
232             }
233             return;
234         }
235         WallpaperTileInfo info = (WallpaperTileInfo) v.getTag();
236         if (info.isSelectable() && v.getVisibility() == View.VISIBLE) {
237             selectTile(v);
238             setWallpaperButtonEnabled(true);
239         }
240         info.onClick(this);
241     }
242 
243     /**
244      * Called when a view is long clicked
245      */
246     @Override
onLongClick(View v)247     public boolean onLongClick(View v) {
248         CheckableFrameLayout c = (CheckableFrameLayout) v;
249         c.toggle();
250 
251         if (mActionMode != null) {
252             mActionMode.invalidate();
253         } else {
254             // Start the CAB using the ActionMode.Callback defined below
255             mActionMode = startActionMode(this);
256             int childCount = mWallpapersView.getChildCount();
257             for (int i = 0; i < childCount; i++) {
258                 mWallpapersView.getChildAt(i).setSelected(false);
259             }
260         }
261         return true;
262     }
263 
setWallpaperButtonEnabled(boolean enabled)264     public void setWallpaperButtonEnabled(boolean enabled) {
265         mSetWallpaperButton.setEnabled(enabled);
266     }
267 
getWallpaperParallaxOffset()268     public float getWallpaperParallaxOffset() {
269         return mWallpaperParallaxOffset;
270     }
271 
selectTile(View v)272     public void selectTile(View v) {
273         if (mSelectedTile != null) {
274             mSelectedTile.setSelected(false);
275             mSelectedTile = null;
276         }
277         mSelectedTile = v;
278         v.setSelected(true);
279         mSelectedIndex = mWallpapersView.indexOfChild(v);
280         // TODO: Remove this once the accessibility framework and
281         // services have better support for selection state.
282         v.announceForAccessibility(
283             getString(R.string.announce_selection, v.getContentDescription()));
284     }
285 
initializeScrollForRtl()286     private void initializeScrollForRtl() {
287         if (WallpaperUtils.isRtl(getResources())) {
288             final ViewTreeObserver observer = mWallpaperScrollContainer.getViewTreeObserver();
289             observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
290                 public void onGlobalLayout() {
291                     LinearLayout masterWallpaperList =
292                             (LinearLayout) findViewById(R.id.master_wallpaper_list);
293                     mWallpaperScrollContainer.scrollTo(masterWallpaperList.getWidth(), 0);
294                     mWallpaperScrollContainer.getViewTreeObserver().removeOnGlobalLayoutListener(this);
295                 }
296             });
297         }
298     }
299 
onStop()300     public void onStop() {
301         super.onStop();
302         mWallpaperStrip = findViewById(R.id.wallpaper_strip);
303         if (mWallpaperStrip.getAlpha() < 1f) {
304             mWallpaperStrip.setAlpha(1f);
305             mWallpaperStrip.setVisibility(View.VISIBLE);
306         }
307     }
308 
onSaveInstanceState(Bundle outState)309     public void onSaveInstanceState(Bundle outState) {
310         outState.putParcelableArrayList(TEMP_WALLPAPER_TILES, mTempWallpaperTiles);
311         outState.putInt(SELECTED_INDEX, mSelectedIndex);
312     }
313 
onRestoreInstanceState(Bundle savedInstanceState)314     protected void onRestoreInstanceState(Bundle savedInstanceState) {
315         ArrayList<Uri> uris = savedInstanceState.getParcelableArrayList(TEMP_WALLPAPER_TILES);
316         for (Uri uri : uris) {
317             addTemporaryWallpaperTile(uri, true);
318         }
319         mSelectedIndex = savedInstanceState.getInt(SELECTED_INDEX, -1);
320     }
321 
updateTileIndices()322     private void updateTileIndices() {
323         LinearLayout masterWallpaperList = (LinearLayout) findViewById(R.id.master_wallpaper_list);
324         final int childCount = masterWallpaperList.getChildCount();
325         final Resources res = getResources();
326 
327         // Do two passes; the first pass gets the total number of tiles
328         int numTiles = 0;
329         for (int passNum = 0; passNum < 2; passNum++) {
330             int tileIndex = 0;
331             for (int i = 0; i < childCount; i++) {
332                 View child = masterWallpaperList.getChildAt(i);
333                 LinearLayout subList;
334 
335                 int subListStart;
336                 int subListEnd;
337                 if (child.getTag() instanceof WallpaperTileInfo) {
338                     subList = masterWallpaperList;
339                     subListStart = i;
340                     subListEnd = i + 1;
341                 } else { // if (child instanceof LinearLayout) {
342                     subList = (LinearLayout) child;
343                     subListStart = 0;
344                     subListEnd = subList.getChildCount();
345                 }
346 
347                 for (int j = subListStart; j < subListEnd; j++) {
348                     WallpaperTileInfo info = (WallpaperTileInfo) subList.getChildAt(j).getTag();
349                     if (info.isNamelessWallpaper()) {
350                         if (passNum == 0) {
351                             numTiles++;
352                         } else {
353                             CharSequence label = res.getString(
354                                     R.string.wallpaper_accessibility_name, ++tileIndex, numTiles);
355                             info.onIndexUpdated(label);
356                         }
357                     }
358                 }
359             }
360         }
361     }
362 
addTemporaryWallpaperTile(final Uri uri, boolean fromRestore)363     private void addTemporaryWallpaperTile(final Uri uri, boolean fromRestore) {
364 
365         // Add a tile for the image picked from Gallery, reusing the existing tile if there is one.
366         View imageTile = null;
367         int indexOfExistingTile = 0;
368         for (; indexOfExistingTile < mWallpapersView.getChildCount(); indexOfExistingTile++) {
369             View thumbnail = mWallpapersView.getChildAt(indexOfExistingTile);
370             Object tag = thumbnail.getTag();
371             if (tag instanceof UriWallpaperInfo && ((UriWallpaperInfo) tag).mUri.equals(uri)) {
372                 imageTile = thumbnail;
373                 break;
374             }
375         }
376         final UriWallpaperInfo info;
377         if (imageTile != null) {
378             // Always move the existing wallpaper to the front so user can see it without scrolling.
379             mWallpapersView.removeViewAt(indexOfExistingTile);
380             info = (UriWallpaperInfo) imageTile.getTag();
381         } else {
382             // This is the first time this temporary wallpaper has been added
383             info = new UriWallpaperInfo(uri);
384             imageTile = createTileView(mWallpapersView, info, true);
385             mTempWallpaperTiles.add(uri);
386         }
387         mWallpapersView.addView(imageTile, 0);
388         info.loadThumbnaleAsync(this);
389 
390         updateTileIndices();
391         if (!fromRestore) {
392             onClick(imageTile);
393         }
394     }
395 
populateWallpapers(ViewGroup parent, List<? extends WallpaperTileInfo> wallpapers, boolean addLongPressHandler)396     private void populateWallpapers(ViewGroup parent, List<? extends WallpaperTileInfo> wallpapers,
397             boolean addLongPressHandler) {
398         for (WallpaperTileInfo info : wallpapers) {
399             parent.addView(createTileView(parent, info, addLongPressHandler));
400         }
401     }
402 
createTileView(ViewGroup parent, WallpaperTileInfo info, boolean addLongPress)403     private View createTileView(ViewGroup parent, WallpaperTileInfo info, boolean addLongPress) {
404         View view = info.createView(this, getLayoutInflater(), parent);
405         view.setTag(info);
406 
407         if (addLongPress) {
408             view.setOnLongClickListener(this);
409         }
410         view.setOnClickListener(this);
411         return view;
412     }
413 
onActivityResult(int requestCode, int resultCode, Intent data)414     public void onActivityResult(int requestCode, int resultCode, Intent data) {
415         if (requestCode == IMAGE_PICK && resultCode == Activity.RESULT_OK) {
416             if (data != null && data.getData() != null) {
417                 Uri uri = data.getData();
418                 addTemporaryWallpaperTile(uri, false);
419             }
420         } else if (requestCode == PICK_WALLPAPER_THIRD_PARTY_ACTIVITY
421                 && resultCode == Activity.RESULT_OK) {
422             // Something was set on the third-party activity.
423             setResult(Activity.RESULT_OK);
424             finish();
425         }
426     }
427 
findBundledWallpapers()428     public ArrayList<WallpaperTileInfo> findBundledWallpapers() {
429         final ArrayList<WallpaperTileInfo> bundled = new ArrayList<WallpaperTileInfo>(24);
430         Pair<ApplicationInfo, Integer> r = getWallpaperArrayResourceId();
431         if (r != null) {
432             try {
433                 Resources wallpaperRes = getPackageManager().getResourcesForApplication(r.first);
434                 addWallpapers(bundled, wallpaperRes, r.first.packageName, r.second);
435             } catch (PackageManager.NameNotFoundException e) {
436             }
437         }
438 
439         // Add an entry for the default wallpaper (stored in system resources)
440         WallpaperTileInfo defaultWallpaperInfo = DefaultWallpaperInfo.get(this);
441         if (defaultWallpaperInfo != null) {
442             bundled.add(0, defaultWallpaperInfo);
443         }
444         return bundled;
445     }
446 
getWallpaperArrayResourceId()447     public Pair<ApplicationInfo, Integer> getWallpaperArrayResourceId() {
448         return new Pair<>(getApplicationInfo(), R.array.wallpapers);
449     }
450 
addWallpapers(ArrayList<WallpaperTileInfo> known, Resources res, String packageName, int listResId)451     public void addWallpapers(ArrayList<WallpaperTileInfo> known, Resources res,
452             String packageName, int listResId) {
453         final String[] extras = res.getStringArray(listResId);
454         for (String extra : extras) {
455             int resId = res.getIdentifier(extra, "drawable", packageName);
456             if (resId != 0) {
457                 final int thumbRes = res.getIdentifier(extra + "_small", "drawable", packageName);
458 
459                 if (thumbRes != 0) {
460                     ResourceWallpaperInfo wallpaperInfo =
461                             new ResourceWallpaperInfo(res, resId, res.getDrawable(thumbRes));
462                     known.add(wallpaperInfo);
463                     // Log.d(TAG, "add: [" + packageName + "]: " + extra + " (" + res + ")");
464                 }
465             } else {
466                 Log.e(TAG, "Couldn't find wallpaper " + extra);
467             }
468         }
469     }
470 
getSavedImages()471     public SavedWallpaperImages getSavedImages() {
472         return mSavedImages;
473     }
474 
startActivityForResultSafely(Intent intent, int requestCode)475     public void startActivityForResultSafely(Intent intent, int requestCode) {
476         startActivityForResult(intent, requestCode);
477     }
478 
479     // CAB for deleting items
480     /**
481      * Called when the action mode is created; startActionMode() was called
482      */
483     @Override
onCreateActionMode(ActionMode mode, Menu menu)484     public boolean onCreateActionMode(ActionMode mode, Menu menu) {
485         // Inflate a menu resource providing context menu items
486         MenuInflater inflater = mode.getMenuInflater();
487         inflater.inflate(R.menu.cab_delete_wallpapers, menu);
488         return true;
489     }
490 
491     /**
492      * Called each time the action mode is shown. Always called after onCreateActionMode,
493      * but may be called multiple times if the mode is invalidated.
494      */
495     @Override
onPrepareActionMode(ActionMode mode, Menu menu)496     public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
497         int childCount = mWallpapersView.getChildCount();
498         int numCheckedItems = 0;
499         for (int i = 0; i < childCount; i++) {
500             CheckableFrameLayout c = (CheckableFrameLayout) mWallpapersView.getChildAt(i);
501             if (c.isChecked()) {
502                 numCheckedItems++;
503             }
504         }
505 
506         if (numCheckedItems == 0) {
507             mode.finish();
508             return true;
509         } else {
510             mode.setTitle(getResources().getQuantityString(
511                     R.plurals.number_of_items_selected, numCheckedItems, numCheckedItems));
512             return true;
513         }
514     }
515 
516     /**
517      * Called when the user selects a contextual menu item
518      */
519     @Override
onActionItemClicked(ActionMode mode, MenuItem item)520     public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
521         int itemId = item.getItemId();
522         if (itemId == R.id.menu_delete) {
523             int childCount = mWallpapersView.getChildCount();
524             ArrayList<View> viewsToRemove = new ArrayList<View>();
525             boolean selectedTileRemoved = false;
526             for (int i = 0; i < childCount; i++) {
527                 CheckableFrameLayout c =
528                         (CheckableFrameLayout) mWallpapersView.getChildAt(i);
529                 if (c.isChecked()) {
530                     WallpaperTileInfo info = (WallpaperTileInfo) c.getTag();
531                     info.onDelete(WallpaperPickerActivity.this);
532                     viewsToRemove.add(c);
533                     if (i == mSelectedIndex) {
534                         selectedTileRemoved = true;
535                     }
536                 }
537             }
538             for (View v : viewsToRemove) {
539                 mWallpapersView.removeView(v);
540             }
541             if (selectedTileRemoved) {
542                 mSelectedIndex = -1;
543                 mSelectedTile = null;
544                 setSystemWallpaperVisiblity(true);
545             }
546             updateTileIndices();
547             mode.finish(); // Action picked, so close the CAB
548             return true;
549         } else {
550             return false;
551         }
552     }
553 
554     /**
555      * Called when the user exits the action mode
556      */
557     @Override
onDestroyActionMode(ActionMode mode)558     public void onDestroyActionMode(ActionMode mode) {
559         int childCount = mWallpapersView.getChildCount();
560         for (int i = 0; i < childCount; i++) {
561             CheckableFrameLayout c = (CheckableFrameLayout) mWallpapersView.getChildAt(i);
562             c.setChecked(false);
563         }
564         if (mSelectedTile != null) {
565             mSelectedTile.setSelected(true);
566         }
567         mActionMode = null;
568     }
569 }
570