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