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