From 65d60e21ec22e3bf03ba39f7a0be24df2cc914a2 Mon Sep 17 00:00:00 2001 From: Adrian Roos Date: Tue, 15 Apr 2014 21:07:49 +0200 Subject: Fix skia crash in widget preview loading This fix works making sure all drawables are mutated before drawing them in the background. Mutation has to be executed on the main thread because that operation is not thread safe. Bug: 12525890 Change-Id: Id7bdf9cf48d3e7b7f31938bdea4a3bf1632cf337 --- src/com/android/launcher3/MainThreadExecutor.java | 80 ++++++++++++++++++++++ src/com/android/launcher3/WidgetPreviewLoader.java | 27 +++++++- 2 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 src/com/android/launcher3/MainThreadExecutor.java diff --git a/src/com/android/launcher3/MainThreadExecutor.java b/src/com/android/launcher3/MainThreadExecutor.java new file mode 100644 index 000000000..866b17c71 --- /dev/null +++ b/src/com/android/launcher3/MainThreadExecutor.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.launcher3; + +import android.os.Handler; +import android.os.Looper; + +import java.util.List; +import java.util.concurrent.AbstractExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * An executor service that executes its tasks on the main thread. + * + * Shutting down this executor is not supported. + */ +public class MainThreadExecutor extends AbstractExecutorService { + + private Handler mHandler = new Handler(Looper.getMainLooper()); + + @Override + public void execute(Runnable runnable) { + if (Looper.getMainLooper() == Looper.myLooper()) { + runnable.run(); + } else { + mHandler.post(runnable); + } + } + + /** + * Not supported and throws an exception when used. + */ + @Override + @Deprecated + public void shutdown() { + throw new UnsupportedOperationException(); + } + + /** + * Not supported and throws an exception when used. + */ + @Override + @Deprecated + public List shutdownNow() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isShutdown() { + return false; + } + + @Override + public boolean isTerminated() { + return false; + } + + /** + * Not supported and throws an exception when used. + */ + @Override + @Deprecated + public boolean awaitTermination(long l, TimeUnit timeUnit) throws InterruptedException { + throw new UnsupportedOperationException(); + } +} diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java index 36152f807..5dad2a860 100644 --- a/src/com/android/launcher3/WidgetPreviewLoader.java +++ b/src/com/android/launcher3/WidgetPreviewLoader.java @@ -35,6 +35,8 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; abstract class SoftReferenceThreadLocal { private ThreadLocal> mThreadLocal; @@ -137,6 +139,8 @@ public class WidgetPreviewLoader { private final ArrayList> mUnusedBitmaps; private final static HashSet sInvalidPackages; + private final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor(); + static { sInvalidPackages = new HashSet(); } @@ -492,7 +496,8 @@ public class WidgetPreviewLoader { Drawable drawable = null; if (previewImage != 0) { - drawable = mPackageManager.getDrawable(packageName, previewImage, null); + drawable = mutateOnMainThread( + mPackageManager.getDrawable(packageName, previewImage, null)); if (drawable == null) { Log.w(TAG, "Can't load widget preview drawable 0x" + Integer.toHexString(previewImage) + " for provider: " + provider); @@ -511,6 +516,7 @@ public class WidgetPreviewLoader { if (cellHSpan < 1) cellHSpan = 1; if (cellVSpan < 1) cellVSpan = 1; + // This Drawable is not directly drawn, so there's no need to mutate it. BitmapDrawable previewDrawable = (BitmapDrawable) mContext.getResources() .getDrawable(R.drawable.widget_tile); final int previewDrawableWidth = previewDrawable @@ -548,7 +554,7 @@ public class WidgetPreviewLoader { int yoffset = (int) ((previewDrawableHeight - mAppIconSize * iconScale) / 2); if (iconId > 0) - icon = mIconCache.getFullResIcon(packageName, iconId); + icon = mutateOnMainThread(mIconCache.getFullResIcon(packageName, iconId)); if (icon != null) { renderDrawableToBitmap(icon, defaultPreview, hoffset, yoffset, (int) (mAppIconSize * iconScale), @@ -617,7 +623,7 @@ public class WidgetPreviewLoader { c.setBitmap(null); } // Render the icon - Drawable icon = mIconCache.getFullResIcon(info); + Drawable icon = mutateOnMainThread(mIconCache.getFullResIcon(info)); int paddingTop = mContext. getResources().getDimensionPixelOffset(R.dimen.shortcut_preview_padding_top); @@ -677,4 +683,19 @@ public class WidgetPreviewLoader { } } + private Drawable mutateOnMainThread(final Drawable drawable) { + try { + return mMainThreadExecutor.submit(new Callable() { + @Override + public Drawable call() throws Exception { + return drawable.mutate(); + } + }).get(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException(e); + } catch (ExecutionException e) { + throw new RuntimeException(e); + } + } } -- cgit v1.2.3