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