Home | History | Annotate | Download | only in wallpaperpicker
      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      */
     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 
    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
    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      */
    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
    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
    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 
    264     public void setWallpaperButtonEnabled(boolean enabled) {
    265         mSetWallpaperButton.setEnabled(enabled);
    266     }
    267 
    268     public float getWallpaperParallaxOffset() {
    269         return mWallpaperParallaxOffset;
    270     }
    271 
    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 
    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 
    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 
    309     public void onSaveInstanceState(Bundle outState) {
    310         outState.putParcelableArrayList(TEMP_WALLPAPER_TILES, mTempWallpaperTiles);
    311         outState.putInt(SELECTED_INDEX, mSelectedIndex);
    312     }
    313 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    447     public Pair<ApplicationInfo, Integer> getWallpaperArrayResourceId() {
    448         return new Pair<>(getApplicationInfo(), R.array.wallpapers);
    449     }
    450 
    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 
    471     public SavedWallpaperImages getSavedImages() {
    472         return mSavedImages;
    473     }
    474 
    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
    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
    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
    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
    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