WallpaperCropActivity.java revision 7b215cb92288aa0a21bc773511ddd537b8fbb459
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 try { 251 if (path != null) { 252 ei.readExif(path); 253 } else if (uri != null) { 254 InputStream is = context.getContentResolver().openInputStream(uri); 255 BufferedInputStream bis = new BufferedInputStream(is); 256 ei.readExif(bis); 257 bis.close(); 258 } else { 259 InputStream is = res.openRawResource(resId); 260 BufferedInputStream bis = new BufferedInputStream(is); 261 ei.readExif(bis); 262 bis.close(); 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 } 271 return 0; 272 } 273 274 protected void setWallpaper(String filePath, final boolean finishActivityWhenDone) { 275 int rotation = getRotationFromExif(filePath); 276 BitmapCropTask cropTask = new BitmapCropTask( 277 this, filePath, null, rotation, 0, 0, true, false, null); 278 final Point bounds = cropTask.getImageBounds(); 279 Runnable onEndCrop = new Runnable() { 280 public void run() { 281 updateWallpaperDimensions(bounds.x, bounds.y); 282 if (finishActivityWhenDone) { 283 setResult(Activity.RESULT_OK); 284 finish(); 285 } 286 } 287 }; 288 cropTask.setOnEndRunnable(onEndCrop); 289 cropTask.setNoCrop(true); 290 cropTask.execute(); 291 } 292 293 protected void cropImageAndSetWallpaper( 294 Resources res, int resId, final boolean finishActivityWhenDone) { 295 // crop this image and scale it down to the default wallpaper size for 296 // this device 297 int rotation = getRotationFromExif(res, resId); 298 Point inSize = mCropView.getSourceDimensions(); 299 Point outSize = getDefaultWallpaperSize(getResources(), 300 getWindowManager()); 301 RectF crop = getMaxCropRect( 302 inSize.x, inSize.y, outSize.x, outSize.y, false); 303 Runnable onEndCrop = new Runnable() { 304 public void run() { 305 // Passing 0, 0 will cause launcher to revert to using the 306 // default wallpaper size 307 updateWallpaperDimensions(0, 0); 308 if (finishActivityWhenDone) { 309 setResult(Activity.RESULT_OK); 310 finish(); 311 } 312 } 313 }; 314 BitmapCropTask cropTask = new BitmapCropTask(this, res, resId, 315 crop, rotation, outSize.x, outSize.y, true, false, onEndCrop); 316 cropTask.execute(); 317 } 318 319 private static boolean isScreenLarge(Resources res) { 320 Configuration config = res.getConfiguration(); 321 return config.smallestScreenWidthDp >= 720; 322 } 323 324 protected void cropImageAndSetWallpaper(Uri uri, 325 OnBitmapCroppedHandler onBitmapCroppedHandler, final boolean finishActivityWhenDone) { 326 // Get the crop 327 boolean ltr = mCropView.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR; 328 329 Point minDims = new Point(); 330 Point maxDims = new Point(); 331 Display d = getWindowManager().getDefaultDisplay(); 332 d.getCurrentSizeRange(minDims, maxDims); 333 334 Point displaySize = new Point(); 335 d.getSize(displaySize); 336 337 int maxDim = Math.max(maxDims.x, maxDims.y); 338 final int minDim = Math.min(minDims.x, minDims.y); 339 int defaultWallpaperWidth; 340 if (isScreenLarge(getResources())) { 341 defaultWallpaperWidth = (int) (maxDim * 342 wallpaperTravelToScreenWidthRatio(maxDim, minDim)); 343 } else { 344 defaultWallpaperWidth = Math.max((int) 345 (minDim * WALLPAPER_SCREENS_SPAN), maxDim); 346 } 347 348 boolean isPortrait = displaySize.x < displaySize.y; 349 int portraitHeight; 350 if (isPortrait) { 351 portraitHeight = mCropView.getHeight(); 352 } else { 353 // TODO: how to actually get the proper portrait height? 354 // This is not quite right: 355 portraitHeight = Math.max(maxDims.x, maxDims.y); 356 } 357 if (android.os.Build.VERSION.SDK_INT >= 358 android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) { 359 Point realSize = new Point(); 360 d.getRealSize(realSize); 361 portraitHeight = Math.max(realSize.x, realSize.y); 362 } 363 // Get the crop 364 RectF cropRect = mCropView.getCrop(); 365 int cropRotation = mCropView.getImageRotation(); 366 float cropScale = mCropView.getWidth() / (float) cropRect.width(); 367 368 Point inSize = mCropView.getSourceDimensions(); 369 Matrix rotateMatrix = new Matrix(); 370 rotateMatrix.setRotate(cropRotation); 371 float[] rotatedInSize = new float[] { inSize.x, inSize.y }; 372 rotateMatrix.mapPoints(rotatedInSize); 373 rotatedInSize[0] = Math.abs(rotatedInSize[0]); 374 rotatedInSize[1] = Math.abs(rotatedInSize[1]); 375 376 // ADJUST CROP WIDTH 377 // Extend the crop all the way to the right, for parallax 378 // (or all the way to the left, in RTL) 379 float extraSpace = ltr ? rotatedInSize[0] - cropRect.right : cropRect.left; 380 // Cap the amount of extra width 381 float maxExtraSpace = defaultWallpaperWidth / cropScale - cropRect.width(); 382 extraSpace = Math.min(extraSpace, maxExtraSpace); 383 384 if (ltr) { 385 cropRect.right += extraSpace; 386 } else { 387 cropRect.left -= extraSpace; 388 } 389 390 // ADJUST CROP HEIGHT 391 if (isPortrait) { 392 cropRect.bottom = cropRect.top + portraitHeight / cropScale; 393 } else { // LANDSCAPE 394 float extraPortraitHeight = 395 portraitHeight / cropScale - cropRect.height(); 396 float expandHeight = 397 Math.min(Math.min(rotatedInSize[1] - cropRect.bottom, cropRect.top), 398 extraPortraitHeight / 2); 399 cropRect.top -= expandHeight; 400 cropRect.bottom += expandHeight; 401 } 402 final int outWidth = (int) Math.round(cropRect.width() * cropScale); 403 final int outHeight = (int) Math.round(cropRect.height() * cropScale); 404 405 Runnable onEndCrop = new Runnable() { 406 public void run() { 407 updateWallpaperDimensions(outWidth, outHeight); 408 if (finishActivityWhenDone) { 409 setResult(Activity.RESULT_OK); 410 finish(); 411 } 412 } 413 }; 414 BitmapCropTask cropTask = new BitmapCropTask(this, uri, 415 cropRect, cropRotation, outWidth, outHeight, true, false, onEndCrop); 416 if (onBitmapCroppedHandler != null) { 417 cropTask.setOnBitmapCropped(onBitmapCroppedHandler); 418 } 419 cropTask.execute(); 420 } 421 422 public interface OnBitmapCroppedHandler { 423 public void onBitmapCropped(byte[] imageBytes); 424 } 425 426 protected static class BitmapCropTask extends AsyncTask<Void, Void, Boolean> { 427 Uri mInUri = null; 428 Context mContext; 429 String mInFilePath; 430 byte[] mInImageBytes; 431 int mInResId = 0; 432 RectF mCropBounds = null; 433 int mOutWidth, mOutHeight; 434 int mRotation; 435 String mOutputFormat = "jpg"; // for now 436 boolean mSetWallpaper; 437 boolean mSaveCroppedBitmap; 438 Bitmap mCroppedBitmap; 439 Runnable mOnEndRunnable; 440 Resources mResources; 441 OnBitmapCroppedHandler mOnBitmapCroppedHandler; 442 boolean mNoCrop; 443 444 public BitmapCropTask(Context c, String filePath, 445 RectF cropBounds, int rotation, int outWidth, int outHeight, 446 boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) { 447 mContext = c; 448 mInFilePath = filePath; 449 init(cropBounds, rotation, 450 outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable); 451 } 452 453 public BitmapCropTask(byte[] imageBytes, 454 RectF cropBounds, int rotation, int outWidth, int outHeight, 455 boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) { 456 mInImageBytes = imageBytes; 457 init(cropBounds, rotation, 458 outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable); 459 } 460 461 public BitmapCropTask(Context c, Uri inUri, 462 RectF cropBounds, int rotation, int outWidth, int outHeight, 463 boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) { 464 mContext = c; 465 mInUri = inUri; 466 init(cropBounds, rotation, 467 outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable); 468 } 469 470 public BitmapCropTask(Context c, Resources res, int inResId, 471 RectF cropBounds, int rotation, int outWidth, int outHeight, 472 boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) { 473 mContext = c; 474 mInResId = inResId; 475 mResources = res; 476 init(cropBounds, rotation, 477 outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable); 478 } 479 480 private void init(RectF cropBounds, int rotation, int outWidth, int outHeight, 481 boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) { 482 mCropBounds = cropBounds; 483 mRotation = rotation; 484 mOutWidth = outWidth; 485 mOutHeight = outHeight; 486 mSetWallpaper = setWallpaper; 487 mSaveCroppedBitmap = saveCroppedBitmap; 488 mOnEndRunnable = onEndRunnable; 489 } 490 491 public void setOnBitmapCropped(OnBitmapCroppedHandler handler) { 492 mOnBitmapCroppedHandler = handler; 493 } 494 495 public void setNoCrop(boolean value) { 496 mNoCrop = value; 497 } 498 499 public void setOnEndRunnable(Runnable onEndRunnable) { 500 mOnEndRunnable = onEndRunnable; 501 } 502 503 // Helper to setup input stream 504 private InputStream regenerateInputStream() { 505 if (mInUri == null && mInResId == 0 && mInFilePath == null && mInImageBytes == null) { 506 Log.w(LOGTAG, "cannot read original file, no input URI, resource ID, or " + 507 "image byte array given"); 508 } else { 509 try { 510 if (mInUri != null) { 511 return new BufferedInputStream( 512 mContext.getContentResolver().openInputStream(mInUri)); 513 } else if (mInFilePath != null) { 514 return mContext.openFileInput(mInFilePath); 515 } else if (mInImageBytes != null) { 516 return new BufferedInputStream(new ByteArrayInputStream(mInImageBytes)); 517 } else { 518 return new BufferedInputStream(mResources.openRawResource(mInResId)); 519 } 520 } catch (FileNotFoundException e) { 521 Log.w(LOGTAG, "cannot read file: " + mInUri.toString(), e); 522 } 523 } 524 return null; 525 } 526 527 public Point getImageBounds() { 528 InputStream is = regenerateInputStream(); 529 if (is != null) { 530 BitmapFactory.Options options = new BitmapFactory.Options(); 531 options.inJustDecodeBounds = true; 532 BitmapFactory.decodeStream(is, null, options); 533 Utils.closeSilently(is); 534 if (options.outWidth != 0 && options.outHeight != 0) { 535 return new Point(options.outWidth, options.outHeight); 536 } 537 } 538 return null; 539 } 540 541 public void setCropBounds(RectF cropBounds) { 542 mCropBounds = cropBounds; 543 } 544 545 public Bitmap getCroppedBitmap() { 546 return mCroppedBitmap; 547 } 548 public boolean cropBitmap() { 549 boolean failure = false; 550 551 552 WallpaperManager wallpaperManager = null; 553 if (mSetWallpaper) { 554 wallpaperManager = WallpaperManager.getInstance(mContext.getApplicationContext()); 555 } 556 557 558 if (mSetWallpaper && mNoCrop) { 559 try { 560 InputStream is = regenerateInputStream(); 561 if (is != null) { 562 wallpaperManager.setStream(is); 563 Utils.closeSilently(is); 564 } 565 } catch (IOException e) { 566 Log.w(LOGTAG, "cannot write stream to wallpaper", e); 567 failure = true; 568 } 569 return !failure; 570 } else { 571 // Find crop bounds (scaled to original image size) 572 Rect roundedTrueCrop = new Rect(); 573 Matrix rotateMatrix = new Matrix(); 574 Matrix inverseRotateMatrix = new Matrix(); 575 if (mRotation > 0) { 576 rotateMatrix.setRotate(mRotation); 577 inverseRotateMatrix.setRotate(-mRotation); 578 579 mCropBounds.roundOut(roundedTrueCrop); 580 mCropBounds = new RectF(roundedTrueCrop); 581 582 Point bounds = getImageBounds(); 583 if (bounds == null) { 584 Log.w(LOGTAG, "cannot get bounds for image"); 585 failure = true; 586 return false; 587 } 588 589 float[] rotatedBounds = new float[] { bounds.x, bounds.y }; 590 rotateMatrix.mapPoints(rotatedBounds); 591 rotatedBounds[0] = Math.abs(rotatedBounds[0]); 592 rotatedBounds[1] = Math.abs(rotatedBounds[1]); 593 594 mCropBounds.offset(-rotatedBounds[0]/2, -rotatedBounds[1]/2); 595 inverseRotateMatrix.mapRect(mCropBounds); 596 mCropBounds.offset(bounds.x/2, bounds.y/2); 597 598 } 599 600 mCropBounds.roundOut(roundedTrueCrop); 601 602 if (roundedTrueCrop.width() <= 0 || roundedTrueCrop.height() <= 0) { 603 Log.w(LOGTAG, "crop has bad values for full size image"); 604 failure = true; 605 return false; 606 } 607 608 // See how much we're reducing the size of the image 609 int scaleDownSampleSize = Math.min(roundedTrueCrop.width() / mOutWidth, 610 roundedTrueCrop.height() / mOutHeight); 611 612 // Attempt to open a region decoder 613 BitmapRegionDecoder decoder = null; 614 try { 615 InputStream is = regenerateInputStream(); 616 if (is == null) { 617 Log.w(LOGTAG, "cannot get input stream for uri=" + mInUri.toString()); 618 failure = true; 619 return false; 620 } 621 decoder = BitmapRegionDecoder.newInstance(is, false); 622 Utils.closeSilently(is); 623 } catch (IOException e) { 624 Log.w(LOGTAG, "cannot open region decoder for file: " + mInUri.toString(), e); 625 } 626 627 Bitmap crop = null; 628 if (decoder != null) { 629 // Do region decoding to get crop bitmap 630 BitmapFactory.Options options = new BitmapFactory.Options(); 631 if (scaleDownSampleSize > 1) { 632 options.inSampleSize = scaleDownSampleSize; 633 } 634 crop = decoder.decodeRegion(roundedTrueCrop, options); 635 decoder.recycle(); 636 } 637 638 if (crop == null) { 639 // BitmapRegionDecoder has failed, try to crop in-memory 640 InputStream is = regenerateInputStream(); 641 Bitmap fullSize = null; 642 if (is != null) { 643 BitmapFactory.Options options = new BitmapFactory.Options(); 644 if (scaleDownSampleSize > 1) { 645 options.inSampleSize = scaleDownSampleSize; 646 } 647 fullSize = BitmapFactory.decodeStream(is, null, options); 648 Utils.closeSilently(is); 649 } 650 if (fullSize != null) { 651 mCropBounds.left /= scaleDownSampleSize; 652 mCropBounds.top /= scaleDownSampleSize; 653 mCropBounds.bottom /= scaleDownSampleSize; 654 mCropBounds.right /= scaleDownSampleSize; 655 mCropBounds.roundOut(roundedTrueCrop); 656 657 crop = Bitmap.createBitmap(fullSize, roundedTrueCrop.left, 658 roundedTrueCrop.top, roundedTrueCrop.width(), 659 roundedTrueCrop.height()); 660 } 661 } 662 663 if (crop == null) { 664 Log.w(LOGTAG, "cannot decode file: " + mInUri.toString()); 665 failure = true; 666 return false; 667 } 668 if (mOutWidth > 0 && mOutHeight > 0 || mRotation > 0) { 669 float[] dimsAfter = new float[] { crop.getWidth(), crop.getHeight() }; 670 rotateMatrix.mapPoints(dimsAfter); 671 dimsAfter[0] = Math.abs(dimsAfter[0]); 672 dimsAfter[1] = Math.abs(dimsAfter[1]); 673 674 if (!(mOutWidth > 0 && mOutHeight > 0)) { 675 mOutWidth = Math.round(dimsAfter[0]); 676 mOutHeight = Math.round(dimsAfter[1]); 677 } 678 679 RectF cropRect = new RectF(0, 0, dimsAfter[0], dimsAfter[1]); 680 RectF returnRect = new RectF(0, 0, mOutWidth, mOutHeight); 681 682 Matrix m = new Matrix(); 683 if (mRotation == 0) { 684 m.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL); 685 } else { 686 Matrix m1 = new Matrix(); 687 m1.setTranslate(-crop.getWidth() / 2f, -crop.getHeight() / 2f); 688 Matrix m2 = new Matrix(); 689 m2.setRotate(mRotation); 690 Matrix m3 = new Matrix(); 691 m3.setTranslate(dimsAfter[0] / 2f, dimsAfter[1] / 2f); 692 Matrix m4 = new Matrix(); 693 m4.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL); 694 695 Matrix c1 = new Matrix(); 696 c1.setConcat(m2, m1); 697 Matrix c2 = new Matrix(); 698 c2.setConcat(m4, m3); 699 m.setConcat(c2, c1); 700 } 701 702 Bitmap tmp = Bitmap.createBitmap((int) returnRect.width(), 703 (int) returnRect.height(), Bitmap.Config.ARGB_8888); 704 if (tmp != null) { 705 Canvas c = new Canvas(tmp); 706 Paint p = new Paint(); 707 p.setFilterBitmap(true); 708 c.drawBitmap(crop, m, p); 709 crop = tmp; 710 } 711 } 712 713 if (mSaveCroppedBitmap) { 714 mCroppedBitmap = crop; 715 } 716 717 // Get output compression format 718 CompressFormat cf = 719 convertExtensionToCompressFormat(getFileExtension(mOutputFormat)); 720 721 // Compress to byte array 722 ByteArrayOutputStream tmpOut = new ByteArrayOutputStream(2048); 723 if (crop.compress(cf, DEFAULT_COMPRESS_QUALITY, tmpOut)) { 724 // If we need to set to the wallpaper, set it 725 if (mSetWallpaper && wallpaperManager != null) { 726 try { 727 byte[] outByteArray = tmpOut.toByteArray(); 728 wallpaperManager.setStream(new ByteArrayInputStream(outByteArray)); 729 if (mOnBitmapCroppedHandler != null) { 730 mOnBitmapCroppedHandler.onBitmapCropped(outByteArray); 731 } 732 } catch (IOException e) { 733 Log.w(LOGTAG, "cannot write stream to wallpaper", e); 734 failure = true; 735 } 736 } 737 } else { 738 Log.w(LOGTAG, "cannot compress bitmap"); 739 failure = true; 740 } 741 } 742 return !failure; // True if any of the operations failed 743 } 744 745 @Override 746 protected Boolean doInBackground(Void... params) { 747 return cropBitmap(); 748 } 749 750 @Override 751 protected void onPostExecute(Boolean result) { 752 if (mOnEndRunnable != null) { 753 mOnEndRunnable.run(); 754 } 755 } 756 } 757 758 protected void updateWallpaperDimensions(int width, int height) { 759 String spKey = getSharedPreferencesKey(); 760 SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_PRIVATE); 761 SharedPreferences.Editor editor = sp.edit(); 762 if (width != 0 && height != 0) { 763 editor.putInt(WALLPAPER_WIDTH_KEY, width); 764 editor.putInt(WALLPAPER_HEIGHT_KEY, height); 765 } else { 766 editor.remove(WALLPAPER_WIDTH_KEY); 767 editor.remove(WALLPAPER_HEIGHT_KEY); 768 } 769 editor.commit(); 770 771 suggestWallpaperDimension(getResources(), 772 sp, getWindowManager(), WallpaperManager.getInstance(this)); 773 } 774 775 static public void suggestWallpaperDimension(Resources res, 776 final SharedPreferences sharedPrefs, 777 WindowManager windowManager, 778 final WallpaperManager wallpaperManager) { 779 final Point defaultWallpaperSize = getDefaultWallpaperSize(res, windowManager); 780 781 new Thread("suggestWallpaperDimension") { 782 public void run() { 783 // If we have saved a wallpaper width/height, use that instead 784 int savedWidth = sharedPrefs.getInt(WALLPAPER_WIDTH_KEY, defaultWallpaperSize.x); 785 int savedHeight = sharedPrefs.getInt(WALLPAPER_HEIGHT_KEY, defaultWallpaperSize.y); 786 wallpaperManager.suggestDesiredDimensions(savedWidth, savedHeight); 787 } 788 }.start(); 789 } 790 791 protected static RectF getMaxCropRect( 792 int inWidth, int inHeight, int outWidth, int outHeight, boolean leftAligned) { 793 RectF cropRect = new RectF(); 794 // Get a crop rect that will fit this 795 if (inWidth / (float) inHeight > outWidth / (float) outHeight) { 796 cropRect.top = 0; 797 cropRect.bottom = inHeight; 798 cropRect.left = (inWidth - (outWidth / (float) outHeight) * inHeight) / 2; 799 cropRect.right = inWidth - cropRect.left; 800 if (leftAligned) { 801 cropRect.right -= cropRect.left; 802 cropRect.left = 0; 803 } 804 } else { 805 cropRect.left = 0; 806 cropRect.right = inWidth; 807 cropRect.top = (inHeight - (outHeight / (float) outWidth) * inWidth) / 2; 808 cropRect.bottom = inHeight - cropRect.top; 809 } 810 return cropRect; 811 } 812 813 protected static CompressFormat convertExtensionToCompressFormat(String extension) { 814 return extension.equals("png") ? CompressFormat.PNG : CompressFormat.JPEG; 815 } 816 817 protected static String getFileExtension(String requestFormat) { 818 String outputFormat = (requestFormat == null) 819 ? "jpg" 820 : requestFormat; 821 outputFormat = outputFormat.toLowerCase(); 822 return (outputFormat.equals("png") || outputFormat.equals("gif")) 823 ? "png" // We don't support gif compression. 824 : "jpg"; 825 } 826} 827