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 if (mRotation > 0) { 554 rotateMatrix.setRotate(mRotation); 555 inverseRotateMatrix.setRotate(-mRotation); 556 557 mCropBounds.roundOut(roundedTrueCrop); 558 mCropBounds = new RectF(roundedTrueCrop); 559 560 Point bounds = getImageBounds(); 561 if (bounds == null) { 562 Log.w(LOGTAG, "cannot get bounds for image"); 563 failure = true; 564 return false; 565 } 566 567 float[] rotatedBounds = new float[] { bounds.x, bounds.y }; 568 rotateMatrix.mapPoints(rotatedBounds); 569 rotatedBounds[0] = Math.abs(rotatedBounds[0]); 570 rotatedBounds[1] = Math.abs(rotatedBounds[1]); 571 572 mCropBounds.offset(-rotatedBounds[0]/2, -rotatedBounds[1]/2); 573 inverseRotateMatrix.mapRect(mCropBounds); 574 mCropBounds.offset(bounds.x/2, bounds.y/2); 575 576 } 577 578 mCropBounds.roundOut(roundedTrueCrop); 579 580 if (roundedTrueCrop.width() <= 0 || roundedTrueCrop.height() <= 0) { 581 Log.w(LOGTAG, "crop has bad values for full size image"); 582 failure = true; 583 return false; 584 } 585 586 // See how much we're reducing the size of the image 587 int scaleDownSampleSize = Math.max(1, Math.min(roundedTrueCrop.width() / mOutWidth, 588 roundedTrueCrop.height() / mOutHeight)); 589 // Attempt to open a region decoder 590 BitmapRegionDecoder decoder = null; 591 InputStream is = null; 592 try { 593 is = regenerateInputStream(); 594 if (is == null) { 595 Log.w(LOGTAG, "cannot get input stream for uri=" + mInUri.toString()); 596 failure = true; 597 return false; 598 } 599 decoder = BitmapRegionDecoder.newInstance(is, false); 600 Utils.closeSilently(is); 601 } catch (IOException e) { 602 Log.w(LOGTAG, "cannot open region decoder for file: " + mInUri.toString(), e); 603 } finally { 604 Utils.closeSilently(is); 605 is = null; 606 } 607 608 Bitmap crop = null; 609 if (decoder != null) { 610 // Do region decoding to get crop bitmap 611 BitmapFactory.Options options = new BitmapFactory.Options(); 612 if (scaleDownSampleSize > 1) { 613 options.inSampleSize = scaleDownSampleSize; 614 } 615 crop = decoder.decodeRegion(roundedTrueCrop, options); 616 decoder.recycle(); 617 } 618 619 if (crop == null) { 620 // BitmapRegionDecoder has failed, try to crop in-memory 621 is = regenerateInputStream(); 622 Bitmap fullSize = null; 623 if (is != null) { 624 BitmapFactory.Options options = new BitmapFactory.Options(); 625 if (scaleDownSampleSize > 1) { 626 options.inSampleSize = scaleDownSampleSize; 627 } 628 fullSize = BitmapFactory.decodeStream(is, null, options); 629 Utils.closeSilently(is); 630 } 631 if (fullSize != null) { 632 mCropBounds.left /= scaleDownSampleSize; 633 mCropBounds.top /= scaleDownSampleSize; 634 mCropBounds.bottom /= scaleDownSampleSize; 635 mCropBounds.right /= scaleDownSampleSize; 636 mCropBounds.roundOut(roundedTrueCrop); 637 638 crop = Bitmap.createBitmap(fullSize, roundedTrueCrop.left, 639 roundedTrueCrop.top, roundedTrueCrop.width(), 640 roundedTrueCrop.height()); 641 } 642 } 643 644 if (crop == null) { 645 Log.w(LOGTAG, "cannot decode file: " + mInUri.toString()); 646 failure = true; 647 return false; 648 } 649 if (mOutWidth > 0 && mOutHeight > 0 || mRotation > 0) { 650 float[] dimsAfter = new float[] { crop.getWidth(), crop.getHeight() }; 651 rotateMatrix.mapPoints(dimsAfter); 652 dimsAfter[0] = Math.abs(dimsAfter[0]); 653 dimsAfter[1] = Math.abs(dimsAfter[1]); 654 655 if (!(mOutWidth > 0 && mOutHeight > 0)) { 656 mOutWidth = Math.round(dimsAfter[0]); 657 mOutHeight = Math.round(dimsAfter[1]); 658 } 659 660 RectF cropRect = new RectF(0, 0, dimsAfter[0], dimsAfter[1]); 661 RectF returnRect = new RectF(0, 0, mOutWidth, mOutHeight); 662 663 Matrix m = new Matrix(); 664 if (mRotation == 0) { 665 m.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL); 666 } else { 667 Matrix m1 = new Matrix(); 668 m1.setTranslate(-crop.getWidth() / 2f, -crop.getHeight() / 2f); 669 Matrix m2 = new Matrix(); 670 m2.setRotate(mRotation); 671 Matrix m3 = new Matrix(); 672 m3.setTranslate(dimsAfter[0] / 2f, dimsAfter[1] / 2f); 673 Matrix m4 = new Matrix(); 674 m4.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL); 675 676 Matrix c1 = new Matrix(); 677 c1.setConcat(m2, m1); 678 Matrix c2 = new Matrix(); 679 c2.setConcat(m4, m3); 680 m.setConcat(c2, c1); 681 } 682 683 Bitmap tmp = Bitmap.createBitmap((int) returnRect.width(), 684 (int) returnRect.height(), Bitmap.Config.ARGB_8888); 685 if (tmp != null) { 686 Canvas c = new Canvas(tmp); 687 Paint p = new Paint(); 688 p.setFilterBitmap(true); 689 c.drawBitmap(crop, m, p); 690 crop = tmp; 691 } 692 } 693 694 if (mSaveCroppedBitmap) { 695 mCroppedBitmap = crop; 696 } 697 698 // Get output compression format 699 CompressFormat cf = 700 convertExtensionToCompressFormat(getFileExtension(mOutputFormat)); 701 702 // Compress to byte array 703 ByteArrayOutputStream tmpOut = new ByteArrayOutputStream(2048); 704 if (crop.compress(cf, DEFAULT_COMPRESS_QUALITY, tmpOut)) { 705 // If we need to set to the wallpaper, set it 706 if (mSetWallpaper && wallpaperManager != null) { 707 try { 708 byte[] outByteArray = tmpOut.toByteArray(); 709 wallpaperManager.setStream(new ByteArrayInputStream(outByteArray)); 710 if (mOnBitmapCroppedHandler != null) { 711 mOnBitmapCroppedHandler.onBitmapCropped(outByteArray); 712 } 713 } catch (IOException e) { 714 Log.w(LOGTAG, "cannot write stream to wallpaper", e); 715 failure = true; 716 } 717 } 718 } else { 719 Log.w(LOGTAG, "cannot compress bitmap"); 720 failure = true; 721 } 722 } 723 return !failure; // True if any of the operations failed 724 } 725 726 @Override 727 protected Boolean doInBackground(Void... params) { 728 return cropBitmap(); 729 } 730 731 @Override 732 protected void onPostExecute(Boolean result) { 733 if (mOnEndRunnable != null) { 734 mOnEndRunnable.run(); 735 } 736 } 737 } 738 739 protected void updateWallpaperDimensions(int width, int height) { 740 String spKey = getSharedPreferencesKey(); 741 SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_MULTI_PROCESS); 742 SharedPreferences.Editor editor = sp.edit(); 743 if (width != 0 && height != 0) { 744 editor.putInt(WALLPAPER_WIDTH_KEY, width); 745 editor.putInt(WALLPAPER_HEIGHT_KEY, height); 746 } else { 747 editor.remove(WALLPAPER_WIDTH_KEY); 748 editor.remove(WALLPAPER_HEIGHT_KEY); 749 } 750 editor.commit(); 751 752 suggestWallpaperDimension(getResources(), 753 sp, getWindowManager(), WallpaperManager.getInstance(this)); 754 } 755 756 static public void suggestWallpaperDimension(Resources res, 757 final SharedPreferences sharedPrefs, 758 WindowManager windowManager, 759 final WallpaperManager wallpaperManager) { 760 final Point defaultWallpaperSize = getDefaultWallpaperSize(res, windowManager); 761 762 new Thread("suggestWallpaperDimension") { 763 public void run() { 764 // If we have saved a wallpaper width/height, use that instead 765 int savedWidth = sharedPrefs.getInt(WALLPAPER_WIDTH_KEY, defaultWallpaperSize.x); 766 int savedHeight = sharedPrefs.getInt(WALLPAPER_HEIGHT_KEY, defaultWallpaperSize.y); 767 wallpaperManager.suggestDesiredDimensions(savedWidth, savedHeight); 768 } 769 }.start(); 770 } 771 772 protected static RectF getMaxCropRect( 773 int inWidth, int inHeight, int outWidth, int outHeight, boolean leftAligned) { 774 RectF cropRect = new RectF(); 775 // Get a crop rect that will fit this 776 if (inWidth / (float) inHeight > outWidth / (float) outHeight) { 777 cropRect.top = 0; 778 cropRect.bottom = inHeight; 779 cropRect.left = (inWidth - (outWidth / (float) outHeight) * inHeight) / 2; 780 cropRect.right = inWidth - cropRect.left; 781 if (leftAligned) { 782 cropRect.right -= cropRect.left; 783 cropRect.left = 0; 784 } 785 } else { 786 cropRect.left = 0; 787 cropRect.right = inWidth; 788 cropRect.top = (inHeight - (outHeight / (float) outWidth) * inWidth) / 2; 789 cropRect.bottom = inHeight - cropRect.top; 790 } 791 return cropRect; 792 } 793 794 protected static CompressFormat convertExtensionToCompressFormat(String extension) { 795 return extension.equals("png") ? CompressFormat.PNG : CompressFormat.JPEG; 796 } 797 798 protected static String getFileExtension(String requestFormat) { 799 String outputFormat = (requestFormat == null) 800 ? "jpg" 801 : requestFormat; 802 outputFormat = outputFormat.toLowerCase(); 803 return (outputFormat.equals("png") || outputFormat.equals("gif")) 804 ? "png" // We don't support gif compression. 805 : "jpg"; 806 } 807} 808