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