summaryrefslogtreecommitdiffstats
path: root/WallpaperPicker/src/com/android/launcher3
diff options
context:
space:
mode:
authorSunny Goyal <sunnygoyal@google.com>2015-02-12 15:34:41 -0800
committerAdam Cohen <adamcohen@google.com>2015-02-24 23:18:25 +0000
commit65b929d6f21683198caf28e266fd2cf52c843764 (patch)
tree17a5b6443943f2d5aa04016c575f7fb09ffb6caa /WallpaperPicker/src/com/android/launcher3
parent31178b8237ccb6af666df60ef60c116c8afdf316 (diff)
downloadandroid_packages_apps_Trebuchet-65b929d6f21683198caf28e266fd2cf52c843764.tar.gz
android_packages_apps_Trebuchet-65b929d6f21683198caf28e266fd2cf52c843764.tar.bz2
android_packages_apps_Trebuchet-65b929d6f21683198caf28e266fd2cf52c843764.zip
Reducing memory usage of wallpaper picker
> Loading preview bitmap only once, instead of loading it twice at BitmapRegionTileSource and BitmapSource > Maintaing a weak-set of reusable bitmaps and reusing them for decoding bitmaps > Loading images on a HandlerThread (instead of AsyncTask) and removing any non-started task before submitting a new task > Loading inbuild images (from resources) on HandlerThread instead of UIThread > Freeing up unbound GL textures before binding a new texture. Bug: 18382606 Change-Id: Ic4ca630dd113ded65d2853eb0d291c9e5823637e (cherry picked from commit 283c2261bd4440f4108a564cea0f5fc499781213)
Diffstat (limited to 'WallpaperPicker/src/com/android/launcher3')
-rw-r--r--WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java195
-rw-r--r--WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java76
2 files changed, 189 insertions, 82 deletions
diff --git a/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java b/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java
index 71c7a161d..0ddb79e4f 100644
--- a/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java
+++ b/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java
@@ -25,25 +25,36 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.RectF;
import android.net.Uri;
-import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
import android.util.Log;
import android.view.Display;
import android.view.View;
import android.view.WindowManager;
import android.widget.Toast;
+
import com.android.gallery3d.common.BitmapCropTask;
import com.android.gallery3d.common.BitmapUtils;
import com.android.gallery3d.common.Utils;
import com.android.photos.BitmapRegionTileSource;
import com.android.photos.BitmapRegionTileSource.BitmapSource;
+import com.android.photos.BitmapRegionTileSource.BitmapSource.InBitmapProvider;
+import com.android.photos.views.TiledImageRenderer.TileSource;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.WeakHashMap;
-public class WallpaperCropActivity extends Activity {
+public class WallpaperCropActivity extends Activity implements Handler.Callback {
private static final String LOGTAG = "Launcher3.CropActivity";
protected static final String WALLPAPER_WIDTH_KEY = "wallpaper.width";
@@ -59,13 +70,29 @@ public class WallpaperCropActivity extends Activity {
public static final int MAX_BMAP_IN_INTENT = 750000;
public static final float WALLPAPER_SCREENS_SPAN = 2f;
+ private static final int MSG_LOAD_IMAGE = 1;
+
protected CropView mCropView;
+ protected View mProgressView;
protected Uri mUri;
protected View mSetWallpaperButton;
+ private HandlerThread mLoaderThread;
+ private Handler mLoaderHandler;
+ private LoadRequest mCurrentLoadRequest;
+ private byte[] mTempStorageForDecoding = new byte[16 * 1024];
+ // A weak-set of reusable bitmaps
+ private Set<Bitmap> mReusableBitmaps =
+ Collections.newSetFromMap(new WeakHashMap<Bitmap, Boolean>());
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+
+ mLoaderThread = new HandlerThread("wallpaper_loader");
+ mLoaderThread.start();
+ mLoaderHandler = new Handler(mLoaderThread.getLooper(), this);
+
init();
if (!enableRotation()) {
setRequestedOrientation(Configuration.ORIENTATION_PORTRAIT);
@@ -76,6 +103,7 @@ public class WallpaperCropActivity extends Activity {
setContentView(R.layout.wallpaper_cropper);
mCropView = (CropView) findViewById(R.id.cropView);
+ mProgressView = findViewById(R.id.loading);
Intent cropIntent = getIntent();
final Uri imageUri = cropIntent.getData();
@@ -116,7 +144,7 @@ public class WallpaperCropActivity extends Activity {
}
}
};
- setCropViewTileSource(bitmapSource, true, false, onLoad);
+ setCropViewTileSource(bitmapSource, true, false, null, onLoad);
}
@Override
@@ -124,65 +152,134 @@ public class WallpaperCropActivity extends Activity {
if (mCropView != null) {
mCropView.destroy();
}
+ if (mLoaderThread != null) {
+ mLoaderThread.quit();
+ }
super.onDestroy();
}
- public void setCropViewTileSource(
- final BitmapRegionTileSource.BitmapSource bitmapSource, final boolean touchEnabled,
- final boolean moveToLeft, final Runnable postExecute) {
- final Context context = WallpaperCropActivity.this;
- final View progressView = findViewById(R.id.loading);
- final AsyncTask<Void, Void, Void> loadBitmapTask = new AsyncTask<Void, Void, Void>() {
- protected Void doInBackground(Void...args) {
- if (!isCancelled()) {
- try {
- bitmapSource.loadInBackground();
- } catch (SecurityException securityException) {
- if (isDestroyed()) {
- // Temporarily granted permissions are revoked when the activity
- // finishes, potentially resulting in a SecurityException here.
- // Even though {@link #isDestroyed} might also return true in different
- // situations where the configuration changes, we are fine with
- // catching these cases here as well.
- cancel(false);
- } else {
- // otherwise it had a different cause and we throw it further
- throw securityException;
+ /**
+ * This is called on {@link #mLoaderThread}
+ */
+ @Override
+ public boolean handleMessage(Message msg) {
+ if (msg.what == MSG_LOAD_IMAGE) {
+ final LoadRequest req = (LoadRequest) msg.obj;
+ try {
+ req.src.loadInBackground(new InBitmapProvider() {
+
+ @Override
+ public Bitmap forPixelCount(int count) {
+ synchronized (mReusableBitmaps) {
+ Iterator<Bitmap> itr = mReusableBitmaps.iterator();
+ while (itr.hasNext()) {
+ Bitmap b = itr.next();
+ if (b.getWidth() * b.getHeight() >= count) {
+ itr.remove();
+ return b;
+ }
+ }
}
+ return null;
}
+ });
+ } catch (SecurityException securityException) {
+ if (isDestroyed()) {
+ // Temporarily granted permissions are revoked when the activity
+ // finishes, potentially resulting in a SecurityException here.
+ // Even though {@link #isDestroyed} might also return true in different
+ // situations where the configuration changes, we are fine with
+ // catching these cases here as well.
+ return true;
+ } else {
+ // otherwise it had a different cause and we throw it further
+ throw securityException;
}
- return null;
}
- protected void onPostExecute(Void arg) {
- if (!isCancelled()) {
- progressView.setVisibility(View.INVISIBLE);
- if (bitmapSource.getLoadingState() == BitmapSource.State.LOADED) {
- mCropView.setTileSource(
- new BitmapRegionTileSource(context, bitmapSource), null);
- mCropView.setTouchEnabled(touchEnabled);
- if (moveToLeft) {
- mCropView.moveToLeft();
- }
+
+ req.result = new BitmapRegionTileSource(this, req.src, mTempStorageForDecoding);
+ runOnUiThread(new Runnable() {
+
+ @Override
+ public void run() {
+ if (req == mCurrentLoadRequest) {
+ onLoadRequestComplete(req,
+ req.src.getLoadingState() == BitmapSource.State.LOADED);
+ } else {
+ addReusableBitmap(req.result);
}
}
- if (postExecute != null) {
- postExecute.run();
+ });
+ return true;
+ }
+ return false;
+ }
+
+ private void addReusableBitmap(TileSource src) {
+ synchronized (mReusableBitmaps) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
+ && src instanceof BitmapRegionTileSource) {
+ Bitmap preview = ((BitmapRegionTileSource) src).getBitmap();
+ if (preview != null && preview.isMutable()) {
+ mReusableBitmaps.add(preview);
}
}
- };
+ }
+ }
+
+ protected void onLoadRequestComplete(LoadRequest req, boolean success) {
+ mCurrentLoadRequest = null;
+ if (success) {
+ TileSource oldSrc = mCropView.getTileSource();
+ mCropView.setTileSource(req.result, null);
+ mCropView.setTouchEnabled(req.touchEnabled);
+ if (req.moveToLeft) {
+ mCropView.moveToLeft();
+ }
+ if (req.scaleProvider != null) {
+ mCropView.setScale(req.scaleProvider.getScale(req.result));
+ }
+
+ // Free last image
+ if (oldSrc != null) {
+ // Call yield instead of recycle, as we only want to free GL resource.
+ // We can still reuse the bitmap for decoding any other image.
+ oldSrc.getPreview().yield();
+ }
+ addReusableBitmap(oldSrc);
+ }
+ if (req.postExecute != null) {
+ req.postExecute.run();
+ }
+ }
+
+ public final void setCropViewTileSource(BitmapSource bitmapSource, boolean touchEnabled,
+ boolean moveToLeft, CropViewScaleProvider scaleProvider, Runnable postExecute) {
+ final LoadRequest req = new LoadRequest();
+ req.moveToLeft = moveToLeft;
+ req.src = bitmapSource;
+ req.touchEnabled = touchEnabled;
+ req.postExecute = postExecute;
+ req.scaleProvider = scaleProvider;
+ mCurrentLoadRequest = req;
+
+ // Remove any pending requests
+ mLoaderHandler.removeMessages(MSG_LOAD_IMAGE);
+ Message.obtain(mLoaderHandler, MSG_LOAD_IMAGE, req).sendToTarget();
+
// We don't want to show the spinner every time we load an image, because that would be
// annoying; instead, only start showing the spinner if loading the image has taken
// longer than 1 sec (ie 1000 ms)
- progressView.postDelayed(new Runnable() {
+ mProgressView.postDelayed(new Runnable() {
public void run() {
- if (loadBitmapTask.getStatus() != AsyncTask.Status.FINISHED) {
- progressView.setVisibility(View.VISIBLE);
+ if (mCurrentLoadRequest == req) {
+ mProgressView.setVisibility(View.VISIBLE);
}
}
}, 1000);
- loadBitmapTask.execute();
}
+
public boolean enableRotation() {
return getResources().getBoolean(R.bool.allow_rotation);
}
@@ -372,4 +469,18 @@ public class WallpaperCropActivity extends Activity {
wallpaperManager.suggestDesiredDimensions(savedWidth, savedHeight);
}
}
+
+ static class LoadRequest {
+ BitmapSource src;
+ boolean touchEnabled;
+ boolean moveToLeft;
+ Runnable postExecute;
+ CropViewScaleProvider scaleProvider;
+
+ TileSource result;
+ }
+
+ interface CropViewScaleProvider {
+ float getScale(TileSource src);
+ }
}
diff --git a/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java b/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java
index f6cc6d0fd..cbed61bd2 100644
--- a/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java
+++ b/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java
@@ -74,6 +74,7 @@ import com.android.gallery3d.common.BitmapUtils;
import com.android.gallery3d.common.Utils;
import com.android.photos.BitmapRegionTileSource;
import com.android.photos.BitmapRegionTileSource.BitmapSource;
+import com.android.photos.views.TiledImageRenderer.TileSource;
import java.io.File;
import java.io.FileOutputStream;
@@ -168,7 +169,7 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
}
mBitmapSource = new BitmapRegionTileSource.UriBitmapSource(
a, mUri, BitmapRegionTileSource.MAX_PREVIEW_SIZE);
- a.setCropViewTileSource(mBitmapSource, true, false, onLoad);
+ a.setCropViewTileSource(mBitmapSource, true, false, null, onLoad);
}
@Override
public void onSave(final WallpaperPickerActivity a) {
@@ -205,7 +206,7 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
public void onClick(WallpaperPickerActivity a) {
BitmapRegionTileSource.UriBitmapSource bitmapSource =
new BitmapRegionTileSource.UriBitmapSource(a, Uri.fromFile(mFile), 1024);
- a.setCropViewTileSource(bitmapSource, false, true, null);
+ a.setCropViewTileSource(bitmapSource, false, true, null, null);
}
@Override
public void onSave(WallpaperPickerActivity a) {
@@ -231,22 +232,22 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
mThumb = thumb;
}
@Override
- public void onClick(WallpaperPickerActivity a) {
+ public void onClick(final WallpaperPickerActivity a) {
BitmapRegionTileSource.ResourceBitmapSource bitmapSource =
new BitmapRegionTileSource.ResourceBitmapSource(
mResources, mResId, BitmapRegionTileSource.MAX_PREVIEW_SIZE);
- bitmapSource.loadInBackground();
- BitmapRegionTileSource source = new BitmapRegionTileSource(a, bitmapSource);
- CropView v = a.getCropView();
- v.setTileSource(source, null);
- Point wallpaperSize = BitmapUtils.getDefaultWallpaperSize(
- a.getResources(), a.getWindowManager());
- RectF crop = Utils.getMaxCropRect(
- source.getImageWidth(), source.getImageHeight(),
- wallpaperSize.x, wallpaperSize.y, false);
- v.setScale(wallpaperSize.x / crop.width());
- v.setTouchEnabled(false);
- a.setSystemWallpaperVisiblity(false);
+ a.setCropViewTileSource(bitmapSource, false, false, new CropViewScaleProvider() {
+
+ @Override
+ public float getScale(TileSource src) {
+ Point wallpaperSize = BitmapUtils.getDefaultWallpaperSize(
+ a.getResources(), a.getWindowManager());
+ RectF crop = Utils.getMaxCropRect(
+ src.getImageWidth(), src.getImageHeight(),
+ wallpaperSize.x, wallpaperSize.y, false);
+ return wallpaperSize.x / crop.width();
+ }
+ }, null);
}
@Override
public void onSave(WallpaperPickerActivity a) {
@@ -271,21 +272,26 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
@Override
public void onClick(WallpaperPickerActivity a) {
CropView c = a.getCropView();
-
Drawable defaultWallpaper = WallpaperManager.getInstance(a).getBuiltInDrawable(
c.getWidth(), c.getHeight(), false, 0.5f, 0.5f);
-
if (defaultWallpaper == null) {
Log.w(TAG, "Null default wallpaper encountered.");
c.setTileSource(null, null);
return;
}
- c.setTileSource(
- new DrawableTileSource(a, defaultWallpaper, DrawableTileSource.MAX_PREVIEW_SIZE), null);
- c.setScale(1f);
- c.setTouchEnabled(false);
- a.setSystemWallpaperVisiblity(false);
+ LoadRequest req = new LoadRequest();
+ req.moveToLeft = false;
+ req.touchEnabled = false;
+ req.scaleProvider = new CropViewScaleProvider() {
+
+ @Override
+ public float getScale(TileSource src) {
+ return 1f;
+ }
+ };
+ req.result = new DrawableTileSource(a, defaultWallpaper, DrawableTileSource.MAX_PREVIEW_SIZE);
+ a.onLoadRequestComplete(req, true);
}
@Override
public void onSave(WallpaperPickerActivity a) {
@@ -348,24 +354,11 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
}
@Override
- public void setCropViewTileSource(BitmapSource bitmapSource,
- boolean touchEnabled,
- boolean moveToLeft,
- final Runnable postExecute) {
- // we also want to show our own wallpaper instead of the one in the background
- Runnable showPostExecuteRunnable = new Runnable() {
- @Override
- public void run() {
- if(postExecute != null) {
- postExecute.run();
- }
- setSystemWallpaperVisiblity(false);
- }
- };
- super.setCropViewTileSource(bitmapSource,
- touchEnabled,
- moveToLeft,
- showPostExecuteRunnable);
+ protected void onLoadRequestComplete(LoadRequest req, boolean success) {
+ super.onLoadRequestComplete(req, success);
+ if (success) {
+ setSystemWallpaperVisiblity(false);
+ }
}
// called by onCreate; this is subclassed to overwrite WallpaperCropActivity
@@ -375,6 +368,9 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
mCropView = (CropView) findViewById(R.id.cropView);
mCropView.setVisibility(View.INVISIBLE);
+ mProgressView = findViewById(R.id.loading);
+
+
mWallpaperStrip = findViewById(R.id.wallpaper_strip);
mCropView.setTouchCallback(new CropView.TouchCallback() {
ViewPropertyAnimator mAnim;