WallpaperCropActivity.java revision 35f922d1d038d14f94b584baae324513610f7813
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/* Copied from Launcher3 */ 17package com.android.wallpapercropper; 18 19import android.app.ActionBar; 20import android.app.Activity; 21import android.app.WallpaperManager; 22import android.content.Context; 23import android.content.Intent; 24import android.content.SharedPreferences; 25import android.content.res.Configuration; 26import android.content.res.Resources; 27import android.graphics.Bitmap; 28import android.graphics.Bitmap.CompressFormat; 29import android.graphics.BitmapFactory; 30import android.graphics.BitmapRegionDecoder; 31import android.graphics.Canvas; 32import android.graphics.Matrix; 33import android.graphics.Paint; 34import android.graphics.Point; 35import android.graphics.Rect; 36import android.graphics.RectF; 37import android.net.Uri; 38import android.os.AsyncTask; 39import android.os.Bundle; 40import android.util.Log; 41import android.view.Display; 42import android.view.View; 43import android.view.WindowManager; 44import android.widget.Toast; 45 46import com.android.gallery3d.common.Utils; 47import com.android.gallery3d.exif.ExifInterface; 48import com.android.photos.BitmapRegionTileSource; 49import com.android.photos.BitmapRegionTileSource.BitmapSource; 50 51import java.io.BufferedInputStream; 52import java.io.ByteArrayInputStream; 53import java.io.ByteArrayOutputStream; 54import java.io.FileNotFoundException; 55import java.io.IOException; 56import java.io.InputStream; 57 58public class WallpaperCropActivity extends Activity { 59 private static final String LOGTAG = "Launcher3.CropActivity"; 60 61 protected static final String WALLPAPER_WIDTH_KEY = "wallpaper.width"; 62 protected static final String WALLPAPER_HEIGHT_KEY = "wallpaper.height"; 63 private static final int DEFAULT_COMPRESS_QUALITY = 90; 64 /** 65 * The maximum bitmap size we allow to be returned through the intent. 66 * Intents have a maximum of 1MB in total size. However, the Bitmap seems to 67 * have some overhead to hit so that we go way below the limit here to make 68 * sure the intent stays below 1MB.We should consider just returning a byte 69 * array instead of a Bitmap instance to avoid overhead. 70 */ 71 public static final int MAX_BMAP_IN_INTENT = 750000; 72 private static final float WALLPAPER_SCREENS_SPAN = 2f; 73 74 protected static Point sDefaultWallpaperSize; 75 76 protected CropView mCropView; 77 protected Uri mUri; 78 79 @Override 80 protected void onCreate(Bundle savedInstanceState) { 81 super.onCreate(savedInstanceState); 82 init(); 83 if (!enableRotation()) { 84 setRequestedOrientation(Configuration.ORIENTATION_PORTRAIT); 85 } 86 } 87 88 protected void init() { 89 setContentView(R.layout.wallpaper_cropper); 90 91 mCropView = (CropView) findViewById(R.id.cropView); 92 93 Intent cropIntent = getIntent(); 94 final Uri imageUri = cropIntent.getData(); 95 96 if (imageUri == null) { 97 Log.e(LOGTAG, "No URI passed in intent, exiting WallpaperCropActivity"); 98 finish(); 99 return; 100 } 101 102 // Action bar 103 // Show the custom action bar view 104 final ActionBar actionBar = getActionBar(); 105 actionBar.setCustomView(R.layout.actionbar_set_wallpaper); 106 actionBar.getCustomView().setOnClickListener( 107 new View.OnClickListener() { 108 @Override 109 public void onClick(View v) { 110 boolean finishActivityWhenDone = true; 111 cropImageAndSetWallpaper(imageUri, null, finishActivityWhenDone); 112 } 113 }); 114 115 // Load image in background 116 final BitmapRegionTileSource.UriBitmapSource bitmapSource = 117 new BitmapRegionTileSource.UriBitmapSource(this, imageUri, 1024); 118 Runnable onLoad = new Runnable() { 119 public void run() { 120 if (bitmapSource.getLoadingState() != BitmapSource.State.LOADED) { 121 Toast.makeText(WallpaperCropActivity.this, 122 getString(R.string.wallpaper_load_fail), 123 Toast.LENGTH_LONG).show(); 124 finish(); 125 } 126 } 127 }; 128 setCropViewTileSource(bitmapSource, true, false, onLoad); 129 } 130 131 public void setCropViewTileSource( 132 final BitmapRegionTileSource.BitmapSource bitmapSource, final boolean touchEnabled, 133 final boolean moveToLeft, final Runnable postExecute) { 134 final Context context = WallpaperCropActivity.this; 135 final View progressView = findViewById(R.id.loading); 136 final AsyncTask<Void, Void, Void> loadBitmapTask = new AsyncTask<Void, Void, Void>() { 137 protected Void doInBackground(Void...args) { 138 if (!isCancelled()) { 139 bitmapSource.loadInBackground(); 140 } 141 return null; 142 } 143 protected void onPostExecute(Void arg) { 144 if (!isCancelled()) { 145 progressView.setVisibility(View.INVISIBLE); 146 if (bitmapSource.getLoadingState() == BitmapSource.State.LOADED) { 147 mCropView.setTileSource( 148 new BitmapRegionTileSource(context, bitmapSource), null); 149 mCropView.setTouchEnabled(touchEnabled); 150 if (moveToLeft) { 151 mCropView.moveToLeft(); 152 } 153 } 154 } 155 if (postExecute != null) { 156 postExecute.run(); 157 } 158 } 159 }; 160 // We don't want to show the spinner every time we load an image, because that would be 161 // annoying; instead, only start showing the spinner if loading the image has taken 162 // longer than 1 sec (ie 1000 ms) 163 progressView.postDelayed(new Runnable() { 164 public void run() { 165 if (loadBitmapTask.getStatus() != AsyncTask.Status.FINISHED) { 166 progressView.setVisibility(View.VISIBLE); 167 } 168 } 169 }, 1000); 170 loadBitmapTask.execute(); 171 } 172 173 public boolean enableRotation() { 174 return getResources().getBoolean(R.bool.allow_rotation); 175 } 176 177 public static String getSharedPreferencesKey() { 178 return WallpaperCropActivity.class.getName(); 179 } 180 181 // As a ratio of screen height, the total distance we want the parallax effect to span 182 // horizontally 183 private static float wallpaperTravelToScreenWidthRatio(int width, int height) { 184 float aspectRatio = width / (float) height; 185 186 // At an aspect ratio of 16/10, the wallpaper parallax effect should span 1.5 * screen width 187 // At an aspect ratio of 10/16, the wallpaper parallax effect should span 1.2 * screen width 188 // We will use these two data points to extrapolate how much the wallpaper parallax effect 189 // to span (ie travel) at any aspect ratio: 190 191 final float ASPECT_RATIO_LANDSCAPE = 16/10f; 192 final float ASPECT_RATIO_PORTRAIT = 10/16f; 193 final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE = 1.5f; 194 final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT = 1.2f; 195 196 // To find out the desired width at different aspect ratios, we use the following two 197 // formulas, where the coefficient on x is the aspect ratio (width/height): 198 // (16/10)x + y = 1.5 199 // (10/16)x + y = 1.2 200 // We solve for x and y and end up with a final formula: 201 final float x = 202 (WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE - WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT) / 203 (ASPECT_RATIO_LANDSCAPE - ASPECT_RATIO_PORTRAIT); 204 final float y = WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT - x * ASPECT_RATIO_PORTRAIT; 205 return x * aspectRatio + y; 206 } 207 208 static protected Point getDefaultWallpaperSize(Resources res, WindowManager windowManager) { 209 if (sDefaultWallpaperSize == null) { 210 Point minDims = new Point(); 211 Point maxDims = new Point(); 212 windowManager.getDefaultDisplay().getCurrentSizeRange(minDims, maxDims); 213 214 int maxDim = Math.max(maxDims.x, maxDims.y); 215 int minDim = Math.max(minDims.x, minDims.y); 216 217 if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) { 218 Point realSize = new Point(); 219 windowManager.getDefaultDisplay().getRealSize(realSize); 220 maxDim = Math.max(realSize.x, realSize.y); 221 minDim = Math.min(realSize.x, realSize.y); 222 } 223 224 // We need to ensure that there is enough extra space in the wallpaper 225 // for the intended parallax effects 226 final int defaultWidth, defaultHeight; 227 if (isScreenLarge(res)) { 228 defaultWidth = (int) (maxDim * wallpaperTravelToScreenWidthRatio(maxDim, minDim)); 229 defaultHeight = maxDim; 230 } else { 231 defaultWidth = Math.max((int) (minDim * WALLPAPER_SCREENS_SPAN), maxDim); 232 defaultHeight = maxDim; 233 } 234 sDefaultWallpaperSize = new Point(defaultWidth, defaultHeight); 235 } 236 return sDefaultWallpaperSize; 237 } 238 239 public static int getRotationFromExif(String path) { 240 return getRotationFromExifHelper(path, null, 0, null, null); 241 } 242 243 public static int getRotationFromExif(Context context, Uri uri) { 244 return getRotationFromExifHelper(null, null, 0, context, uri); 245 } 246 247 public static int getRotationFromExif(Resources res, int resId) { 248 return getRotationFromExifHelper(null, res, resId, null, null); 249 } 250 251 private static int getRotationFromExifHelper( 252 String path, Resources res, int resId, Context context, Uri uri) { 253 ExifInterface ei = new ExifInterface(); 254 InputStream is = null; 255 BufferedInputStream bis = null; 256 try { 257 if (path != null) { 258 ei.readExif(path); 259 } else if (uri != null) { 260 is = context.getContentResolver().openInputStream(uri); 261 bis = new BufferedInputStream(is); 262 ei.readExif(bis); 263 } else { 264 is = res.openRawResource(resId); 265 bis = new BufferedInputStream(is); 266 ei.readExif(bis); 267 } 268 Integer ori = ei.getTagIntValue(ExifInterface.TAG_ORIENTATION); 269 if (ori != null) { 270 return ExifInterface.getRotationForOrientationValue(ori.shortValue()); 271 } 272 } catch (IOException e) { 273 Log.w(LOGTAG, "Getting exif data failed", e); 274 } finally { 275 Utils.closeSilently(bis); 276 Utils.closeSilently(is); 277 } 278 return 0; 279 } 280 281 protected void setWallpaper(String filePath, final boolean finishActivityWhenDone) { 282 int rotation = getRotationFromExif(filePath); 283 BitmapCropTask cropTask = new BitmapCropTask( 284 this, filePath, null, rotation, 0, 0, true, false, null); 285 final Point bounds = cropTask.getImageBounds(); 286 Runnable onEndCrop = new Runnable() { 287 public void run() { 288 updateWallpaperDimensions(bounds.x, bounds.y); 289 if (finishActivityWhenDone) { 290 setResult(Activity.RESULT_OK); 291 finish(); 292 } 293 } 294 }; 295 cropTask.setOnEndRunnable(onEndCrop); 296 cropTask.setNoCrop(true); 297 cropTask.execute(); 298 } 299 300 protected void cropImageAndSetWallpaper( 301 Resources res, int resId, final boolean finishActivityWhenDone) { 302 // crop this image and scale it down to the default wallpaper size for 303 // this device 304 int rotation = getRotationFromExif(res, resId); 305 Point inSize = mCropView.getSourceDimensions(); 306 Point outSize = getDefaultWallpaperSize(getResources(), 307 getWindowManager()); 308 RectF crop = getMaxCropRect( 309 inSize.x, inSize.y, outSize.x, outSize.y, false); 310 Runnable onEndCrop = new Runnable() { 311 public void run() { 312 // Passing 0, 0 will cause launcher to revert to using the 313 // default wallpaper size 314 updateWallpaperDimensions(0, 0); 315 if (finishActivityWhenDone) { 316 setResult(Activity.RESULT_OK); 317 finish(); 318 } 319 } 320 }; 321 BitmapCropTask cropTask = new BitmapCropTask(this, res, resId, 322 crop, rotation, outSize.x, outSize.y, true, false, onEndCrop); 323 cropTask.execute(); 324 } 325 326 private static boolean isScreenLarge(Resources res) { 327 Configuration config = res.getConfiguration(); 328 return config.smallestScreenWidthDp >= 720; 329 } 330 331 protected void cropImageAndSetWallpaper(Uri uri, 332 OnBitmapCroppedHandler onBitmapCroppedHandler, final boolean finishActivityWhenDone) { 333 // Get the crop 334 boolean ltr = mCropView.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR; 335 336 337 Display d = getWindowManager().getDefaultDisplay(); 338 339 Point displaySize = new Point(); 340 d.getSize(displaySize); 341 boolean isPortrait = displaySize.x < displaySize.y; 342 343 Point defaultWallpaperSize = getDefaultWallpaperSize(getResources(), 344 getWindowManager()); 345 // Get the crop 346 RectF cropRect = mCropView.getCrop(); 347 int cropRotation = mCropView.getImageRotation(); 348 float cropScale = mCropView.getWidth() / (float) cropRect.width(); 349 350 Point inSize = mCropView.getSourceDimensions(); 351 Matrix rotateMatrix = new Matrix(); 352 rotateMatrix.setRotate(cropRotation); 353 float[] rotatedInSize = new float[] { inSize.x, inSize.y }; 354 rotateMatrix.mapPoints(rotatedInSize); 355 rotatedInSize[0] = Math.abs(rotatedInSize[0]); 356 rotatedInSize[1] = Math.abs(rotatedInSize[1]); 357 358 // ADJUST CROP WIDTH 359 // Extend the crop all the way to the right, for parallax 360 // (or all the way to the left, in RTL) 361 float extraSpace = ltr ? rotatedInSize[0] - cropRect.right : cropRect.left; 362 // Cap the amount of extra width 363 float maxExtraSpace = defaultWallpaperSize.x / cropScale - cropRect.width(); 364 extraSpace = Math.min(extraSpace, maxExtraSpace); 365 366 if (ltr) { 367 cropRect.right += extraSpace; 368 } else { 369 cropRect.left -= extraSpace; 370 } 371 372 // ADJUST CROP HEIGHT 373 if (isPortrait) { 374 cropRect.bottom = cropRect.top + defaultWallpaperSize.y / cropScale; 375 } else { // LANDSCAPE 376 float extraPortraitHeight = 377 defaultWallpaperSize.y / cropScale - cropRect.height(); 378 float expandHeight = 379 Math.min(Math.min(rotatedInSize[1] - cropRect.bottom, cropRect.top), 380 extraPortraitHeight / 2); 381 cropRect.top -= expandHeight; 382 cropRect.bottom += expandHeight; 383 } 384 final int outWidth = (int) Math.round(cropRect.width() * cropScale); 385 final int outHeight = (int) Math.round(cropRect.height() * cropScale); 386 387 Runnable onEndCrop = new Runnable() { 388 public void run() { 389 updateWallpaperDimensions(outWidth, outHeight); 390 if (finishActivityWhenDone) { 391 setResult(Activity.RESULT_OK); 392 finish(); 393 } 394 } 395 }; 396 BitmapCropTask cropTask = new BitmapCropTask(this, uri, 397 cropRect, cropRotation, outWidth, outHeight, true, false, onEndCrop); 398 if (onBitmapCroppedHandler != null) { 399 cropTask.setOnBitmapCropped(onBitmapCroppedHandler); 400 } 401 cropTask.execute(); 402 } 403 404 public interface OnBitmapCroppedHandler { 405 public void onBitmapCropped(byte[] imageBytes); 406 } 407 408 protected static class BitmapCropTask extends AsyncTask<Void, Void, Boolean> { 409 Uri mInUri = null; 410 Context mContext; 411 String mInFilePath; 412 byte[] mInImageBytes; 413 int mInResId = 0; 414 RectF mCropBounds = null; 415 int mOutWidth, mOutHeight; 416 int mRotation; 417 String mOutputFormat = "jpg"; // for now 418 boolean mSetWallpaper; 419 boolean mSaveCroppedBitmap; 420 Bitmap mCroppedBitmap; 421 Runnable mOnEndRunnable; 422 Resources mResources; 423 OnBitmapCroppedHandler mOnBitmapCroppedHandler; 424 boolean mNoCrop; 425 426 public BitmapCropTask(Context c, String filePath, 427 RectF cropBounds, int rotation, int outWidth, int outHeight, 428 boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) { 429 mContext = c; 430 mInFilePath = filePath; 431 init(cropBounds, rotation, 432 outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable); 433 } 434 435 public BitmapCropTask(byte[] imageBytes, 436 RectF cropBounds, int rotation, int outWidth, int outHeight, 437 boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) { 438 mInImageBytes = imageBytes; 439 init(cropBounds, rotation, 440 outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable); 441 } 442 443 public BitmapCropTask(Context c, Uri inUri, 444 RectF cropBounds, int rotation, int outWidth, int outHeight, 445 boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) { 446 mContext = c; 447 mInUri = inUri; 448 init(cropBounds, rotation, 449 outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable); 450 } 451 452 public BitmapCropTask(Context c, Resources res, int inResId, 453 RectF cropBounds, int rotation, int outWidth, int outHeight, 454 boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) { 455 mContext = c; 456 mInResId = inResId; 457 mResources = res; 458 init(cropBounds, rotation, 459 outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable); 460 } 461 462 private void init(RectF cropBounds, int rotation, int outWidth, int outHeight, 463 boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) { 464 mCropBounds = cropBounds; 465 mRotation = rotation; 466 mOutWidth = outWidth; 467 mOutHeight = outHeight; 468 mSetWallpaper = setWallpaper; 469 mSaveCroppedBitmap = saveCroppedBitmap; 470 mOnEndRunnable = onEndRunnable; 471 } 472 473 public void setOnBitmapCropped(OnBitmapCroppedHandler handler) { 474 mOnBitmapCroppedHandler = handler; 475 } 476 477 public void setNoCrop(boolean value) { 478 mNoCrop = value; 479 } 480 481 public void setOnEndRunnable(Runnable onEndRunnable) { 482 mOnEndRunnable = onEndRunnable; 483 } 484 485 // Helper to setup input stream 486 private InputStream regenerateInputStream() { 487 if (mInUri == null && mInResId == 0 && mInFilePath == null && mInImageBytes == null) { 488 Log.w(LOGTAG, "cannot read original file, no input URI, resource ID, or " + 489 "image byte array given"); 490 } else { 491 try { 492 if (mInUri != null) { 493 return new BufferedInputStream( 494 mContext.getContentResolver().openInputStream(mInUri)); 495 } else if (mInFilePath != null) { 496 return mContext.openFileInput(mInFilePath); 497 } else if (mInImageBytes != null) { 498 return new BufferedInputStream(new ByteArrayInputStream(mInImageBytes)); 499 } else { 500 return new BufferedInputStream(mResources.openRawResource(mInResId)); 501 } 502 } catch (FileNotFoundException e) { 503 Log.w(LOGTAG, "cannot read file: " + mInUri.toString(), e); 504 } 505 } 506 return null; 507 } 508 509 public Point getImageBounds() { 510 InputStream is = regenerateInputStream(); 511 if (is != null) { 512 BitmapFactory.Options options = new BitmapFactory.Options(); 513 options.inJustDecodeBounds = true; 514 BitmapFactory.decodeStream(is, null, options); 515 Utils.closeSilently(is); 516 if (options.outWidth != 0 && options.outHeight != 0) { 517 return new Point(options.outWidth, options.outHeight); 518 } 519 } 520 return null; 521 } 522 523 public void setCropBounds(RectF cropBounds) { 524 mCropBounds = cropBounds; 525 } 526 527 public Bitmap getCroppedBitmap() { 528 return mCroppedBitmap; 529 } 530 public boolean cropBitmap() { 531 boolean failure = false; 532 533 534 WallpaperManager wallpaperManager = null; 535 if (mSetWallpaper) { 536 wallpaperManager = WallpaperManager.getInstance(mContext.getApplicationContext()); 537 } 538 539 540 if (mSetWallpaper && mNoCrop) { 541 try { 542 InputStream is = regenerateInputStream(); 543 if (is != null) { 544 wallpaperManager.setStream(is); 545 Utils.closeSilently(is); 546 } 547 } catch (IOException e) { 548 Log.w(LOGTAG, "cannot write stream to wallpaper", e); 549 failure = true; 550 } 551 return !failure; 552 } else { 553 // Find crop bounds (scaled to original image size) 554 Rect roundedTrueCrop = new Rect(); 555 Matrix rotateMatrix = new Matrix(); 556 Matrix inverseRotateMatrix = new Matrix(); 557 558 Point bounds = getImageBounds(); 559 if (mRotation > 0) { 560 rotateMatrix.setRotate(mRotation); 561 inverseRotateMatrix.setRotate(-mRotation); 562 563 mCropBounds.roundOut(roundedTrueCrop); 564 mCropBounds = new RectF(roundedTrueCrop); 565 566 if (bounds == null) { 567 Log.w(LOGTAG, "cannot get bounds for image"); 568 failure = true; 569 return false; 570 } 571 572 float[] rotatedBounds = new float[] { bounds.x, bounds.y }; 573 rotateMatrix.mapPoints(rotatedBounds); 574 rotatedBounds[0] = Math.abs(rotatedBounds[0]); 575 rotatedBounds[1] = Math.abs(rotatedBounds[1]); 576 577 mCropBounds.offset(-rotatedBounds[0]/2, -rotatedBounds[1]/2); 578 inverseRotateMatrix.mapRect(mCropBounds); 579 mCropBounds.offset(bounds.x/2, bounds.y/2); 580 581 } 582 583 mCropBounds.roundOut(roundedTrueCrop); 584 585 if (roundedTrueCrop.width() <= 0 || roundedTrueCrop.height() <= 0) { 586 Log.w(LOGTAG, "crop has bad values for full size image"); 587 failure = true; 588 return false; 589 } 590 591 // See how much we're reducing the size of the image 592 int scaleDownSampleSize = Math.max(1, Math.min(roundedTrueCrop.width() / mOutWidth, 593 roundedTrueCrop.height() / mOutHeight)); 594 // Attempt to open a region decoder 595 BitmapRegionDecoder decoder = null; 596 InputStream is = null; 597 try { 598 is = regenerateInputStream(); 599 if (is == null) { 600 Log.w(LOGTAG, "cannot get input stream for uri=" + mInUri.toString()); 601 failure = true; 602 return false; 603 } 604 decoder = BitmapRegionDecoder.newInstance(is, false); 605 Utils.closeSilently(is); 606 } catch (IOException e) { 607 Log.w(LOGTAG, "cannot open region decoder for file: " + mInUri.toString(), e); 608 } finally { 609 Utils.closeSilently(is); 610 is = null; 611 } 612 613 Bitmap crop = null; 614 if (decoder != null) { 615 // Do region decoding to get crop bitmap 616 BitmapFactory.Options options = new BitmapFactory.Options(); 617 if (scaleDownSampleSize > 1) { 618 options.inSampleSize = scaleDownSampleSize; 619 } 620 crop = decoder.decodeRegion(roundedTrueCrop, options); 621 decoder.recycle(); 622 } 623 624 if (crop == null) { 625 // BitmapRegionDecoder has failed, try to crop in-memory 626 is = regenerateInputStream(); 627 Bitmap fullSize = null; 628 if (is != null) { 629 BitmapFactory.Options options = new BitmapFactory.Options(); 630 if (scaleDownSampleSize > 1) { 631 options.inSampleSize = scaleDownSampleSize; 632 } 633 fullSize = BitmapFactory.decodeStream(is, null, options); 634 Utils.closeSilently(is); 635 } 636 if (fullSize != null) { 637 // Find out the true sample size that was used by the decoder 638 scaleDownSampleSize = bounds.x / fullSize.getWidth(); 639 mCropBounds.left /= scaleDownSampleSize; 640 mCropBounds.top /= scaleDownSampleSize; 641 mCropBounds.bottom /= scaleDownSampleSize; 642 mCropBounds.right /= scaleDownSampleSize; 643 mCropBounds.roundOut(roundedTrueCrop); 644 645 // Adjust values to account for issues related to rounding 646 if (roundedTrueCrop.width() > fullSize.getWidth()) { 647 // Adjust the width 648 roundedTrueCrop.right = roundedTrueCrop.left + fullSize.getWidth(); 649 } 650 if (roundedTrueCrop.right > fullSize.getWidth()) { 651 // Adjust the left value 652 int adjustment = roundedTrueCrop.left - 653 Math.max(0, roundedTrueCrop.right - roundedTrueCrop.width()); 654 roundedTrueCrop.left -= adjustment; 655 roundedTrueCrop.right -= adjustment; 656 } 657 if (roundedTrueCrop.height() > fullSize.getHeight()) { 658 // Adjust the height 659 roundedTrueCrop.bottom = roundedTrueCrop.top + fullSize.getHeight(); 660 } 661 if (roundedTrueCrop.bottom > fullSize.getHeight()) { 662 // Adjust the top value 663 int adjustment = roundedTrueCrop.top - 664 Math.max(0, roundedTrueCrop.bottom - roundedTrueCrop.height()); 665 roundedTrueCrop.top -= adjustment; 666 roundedTrueCrop.bottom -= adjustment; 667 } 668 669 crop = Bitmap.createBitmap(fullSize, roundedTrueCrop.left, 670 roundedTrueCrop.top, roundedTrueCrop.width(), 671 roundedTrueCrop.height()); 672 } 673 } 674 675 if (crop == null) { 676 Log.w(LOGTAG, "cannot decode file: " + mInUri.toString()); 677 failure = true; 678 return false; 679 } 680 if (mOutWidth > 0 && mOutHeight > 0 || mRotation > 0) { 681 float[] dimsAfter = new float[] { crop.getWidth(), crop.getHeight() }; 682 rotateMatrix.mapPoints(dimsAfter); 683 dimsAfter[0] = Math.abs(dimsAfter[0]); 684 dimsAfter[1] = Math.abs(dimsAfter[1]); 685 686 if (!(mOutWidth > 0 && mOutHeight > 0)) { 687 mOutWidth = Math.round(dimsAfter[0]); 688 mOutHeight = Math.round(dimsAfter[1]); 689 } 690 691 RectF cropRect = new RectF(0, 0, dimsAfter[0], dimsAfter[1]); 692 RectF returnRect = new RectF(0, 0, mOutWidth, mOutHeight); 693 694 Matrix m = new Matrix(); 695 if (mRotation == 0) { 696 m.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL); 697 } else { 698 Matrix m1 = new Matrix(); 699 m1.setTranslate(-crop.getWidth() / 2f, -crop.getHeight() / 2f); 700 Matrix m2 = new Matrix(); 701 m2.setRotate(mRotation); 702 Matrix m3 = new Matrix(); 703 m3.setTranslate(dimsAfter[0] / 2f, dimsAfter[1] / 2f); 704 Matrix m4 = new Matrix(); 705 m4.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL); 706 707 Matrix c1 = new Matrix(); 708 c1.setConcat(m2, m1); 709 Matrix c2 = new Matrix(); 710 c2.setConcat(m4, m3); 711 m.setConcat(c2, c1); 712 } 713 714 Bitmap tmp = Bitmap.createBitmap((int) returnRect.width(), 715 (int) returnRect.height(), Bitmap.Config.ARGB_8888); 716 if (tmp != null) { 717 Canvas c = new Canvas(tmp); 718 Paint p = new Paint(); 719 p.setFilterBitmap(true); 720 c.drawBitmap(crop, m, p); 721 crop = tmp; 722 } 723 } 724 725 if (mSaveCroppedBitmap) { 726 mCroppedBitmap = crop; 727 } 728 729 // Get output compression format 730 CompressFormat cf = 731 convertExtensionToCompressFormat(getFileExtension(mOutputFormat)); 732 733 // Compress to byte array 734 ByteArrayOutputStream tmpOut = new ByteArrayOutputStream(2048); 735 if (crop.compress(cf, DEFAULT_COMPRESS_QUALITY, tmpOut)) { 736 // If we need to set to the wallpaper, set it 737 if (mSetWallpaper && wallpaperManager != null) { 738 try { 739 byte[] outByteArray = tmpOut.toByteArray(); 740 wallpaperManager.setStream(new ByteArrayInputStream(outByteArray)); 741 if (mOnBitmapCroppedHandler != null) { 742 mOnBitmapCroppedHandler.onBitmapCropped(outByteArray); 743 } 744 } catch (IOException e) { 745 Log.w(LOGTAG, "cannot write stream to wallpaper", e); 746 failure = true; 747 } 748 } 749 } else { 750 Log.w(LOGTAG, "cannot compress bitmap"); 751 failure = true; 752 } 753 } 754 return !failure; // True if any of the operations failed 755 } 756 757 @Override 758 protected Boolean doInBackground(Void... params) { 759 return cropBitmap(); 760 } 761 762 @Override 763 protected void onPostExecute(Boolean result) { 764 if (mOnEndRunnable != null) { 765 mOnEndRunnable.run(); 766 } 767 } 768 } 769 770 protected void updateWallpaperDimensions(int width, int height) { 771 String spKey = getSharedPreferencesKey(); 772 SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_MULTI_PROCESS); 773 SharedPreferences.Editor editor = sp.edit(); 774 if (width != 0 && height != 0) { 775 editor.putInt(WALLPAPER_WIDTH_KEY, width); 776 editor.putInt(WALLPAPER_HEIGHT_KEY, height); 777 } else { 778 editor.remove(WALLPAPER_WIDTH_KEY); 779 editor.remove(WALLPAPER_HEIGHT_KEY); 780 } 781 editor.commit(); 782 783 suggestWallpaperDimension(getResources(), 784 sp, getWindowManager(), WallpaperManager.getInstance(this)); 785 } 786 787 static public void suggestWallpaperDimension(Resources res, 788 final SharedPreferences sharedPrefs, 789 WindowManager windowManager, 790 final WallpaperManager wallpaperManager) { 791 final Point defaultWallpaperSize = getDefaultWallpaperSize(res, windowManager); 792 // If we have saved a wallpaper width/height, use that instead 793 int savedWidth = sharedPrefs.getInt(WALLPAPER_WIDTH_KEY, defaultWallpaperSize.x); 794 int savedHeight = sharedPrefs.getInt(WALLPAPER_HEIGHT_KEY, defaultWallpaperSize.y); 795 if (savedWidth != wallpaperManager.getDesiredMinimumWidth() || 796 savedHeight != wallpaperManager.getDesiredMinimumHeight()) { 797 wallpaperManager.suggestDesiredDimensions(savedWidth, savedHeight); 798 } 799 } 800 801 protected static RectF getMaxCropRect( 802 int inWidth, int inHeight, int outWidth, int outHeight, boolean leftAligned) { 803 RectF cropRect = new RectF(); 804 // Get a crop rect that will fit this 805 if (inWidth / (float) inHeight > outWidth / (float) outHeight) { 806 cropRect.top = 0; 807 cropRect.bottom = inHeight; 808 cropRect.left = (inWidth - (outWidth / (float) outHeight) * inHeight) / 2; 809 cropRect.right = inWidth - cropRect.left; 810 if (leftAligned) { 811 cropRect.right -= cropRect.left; 812 cropRect.left = 0; 813 } 814 } else { 815 cropRect.left = 0; 816 cropRect.right = inWidth; 817 cropRect.top = (inHeight - (outHeight / (float) outWidth) * inWidth) / 2; 818 cropRect.bottom = inHeight - cropRect.top; 819 } 820 return cropRect; 821 } 822 823 protected static CompressFormat convertExtensionToCompressFormat(String extension) { 824 return extension.equals("png") ? CompressFormat.PNG : CompressFormat.JPEG; 825 } 826 827 protected static String getFileExtension(String requestFormat) { 828 String outputFormat = (requestFormat == null) 829 ? "jpg" 830 : requestFormat; 831 outputFormat = outputFormat.toLowerCase(); 832 return (outputFormat.equals("png") || outputFormat.equals("gif")) 833 ? "png" // We don't support gif compression. 834 : "jpg"; 835 } 836} 837