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