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