Merge "DPMS: Replace ArrayList<>(1) with SingletonList"
diff --git a/api/current.txt b/api/current.txt
index d54b7fd..8f93361 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -33245,6 +33245,7 @@
     method public void onStopListening();
     method public void onTileAdded();
     method public void onTileRemoved();
+    method public final void showDialog(android.app.Dialog);
     field public static final java.lang.String ACTION_QS_TILE = "android.service.quicksettings.action.QS_TILE";
   }
 
diff --git a/api/system-current.txt b/api/system-current.txt
index 2ea5ebb..7a97280 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -35394,6 +35394,7 @@
     method public void onStopListening();
     method public void onTileAdded();
     method public void onTileRemoved();
+    method public final void showDialog(android.app.Dialog);
     field public static final java.lang.String ACTION_QS_TILE = "android.service.quicksettings.action.QS_TILE";
   }
 
diff --git a/api/test-current.txt b/api/test-current.txt
index 904347d..ddb5b06 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -33247,6 +33247,7 @@
     method public void onStopListening();
     method public void onTileAdded();
     method public void onTileRemoved();
+    method public final void showDialog(android.app.Dialog);
     field public static final java.lang.String ACTION_QS_TILE = "android.service.quicksettings.action.QS_TILE";
   }
 
diff --git a/core/java/android/service/quicksettings/IQSService.aidl b/core/java/android/service/quicksettings/IQSService.aidl
index 087eb61..7e70501 100644
--- a/core/java/android/service/quicksettings/IQSService.aidl
+++ b/core/java/android/service/quicksettings/IQSService.aidl
@@ -23,4 +23,5 @@
  */
 interface IQSService {
     void updateQsTile(in Tile tile);
+    void onShowDialog(in Tile tile);
 }
diff --git a/core/java/android/service/quicksettings/IQSTileService.aidl b/core/java/android/service/quicksettings/IQSTileService.aidl
index 6b46bee5..63a4c5e 100644
--- a/core/java/android/service/quicksettings/IQSTileService.aidl
+++ b/core/java/android/service/quicksettings/IQSTileService.aidl
@@ -27,5 +27,5 @@
     void onTileRemoved();
     void onStartListening();
     void onStopListening();
-    void onClick();
+    void onClick(IBinder wtoken);
 }
diff --git a/core/java/android/service/quicksettings/Tile.java b/core/java/android/service/quicksettings/Tile.java
index c8ae171..a53fc59 100644
--- a/core/java/android/service/quicksettings/Tile.java
+++ b/core/java/android/service/quicksettings/Tile.java
@@ -137,6 +137,18 @@
         }
     }
 
+    /**
+     * @hide
+     * Notifies the IQSService that this tile is showing a dialog.
+     */
+    void onShowDialog() {
+        try {
+            mService.onShowDialog(this);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Couldn't onShowDialog");
+        }
+    }
+
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeStrongInterface(mService);
diff --git a/core/java/android/service/quicksettings/TileService.java b/core/java/android/service/quicksettings/TileService.java
index eba4c6f..fd2d5b0 100644
--- a/core/java/android/service/quicksettings/TileService.java
+++ b/core/java/android/service/quicksettings/TileService.java
@@ -15,6 +15,7 @@
  */
 package android.service.quicksettings;
 
+import android.app.Dialog;
 import android.app.Service;
 import android.content.Intent;
 import android.os.Handler;
@@ -22,6 +23,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
+import android.view.WindowManager;
 
 /**
  * A QSTileService provides the user a tile that can be added to Quick Settings.
@@ -55,7 +57,7 @@
  *     android:icon="@drawable/my_default_icon_label"
  *     android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
  *     <intent-filter>
- *         <action android:name="android.intent.action.QS_TILE" />
+ *         <action android:name="android.service.quicksettings.action.QS_TILE" />
  *     </intent-filter>
  * </service>}
  * </pre>
@@ -73,6 +75,7 @@
 
     private boolean mListening = false;
     private Tile mTile;
+    private IBinder mToken;
 
     /**
      * Called when the user adds this tile to Quick Settings.
@@ -116,6 +119,20 @@
     }
 
     /**
+     * Used to show a dialog.
+     *
+     * This will collapse the Quick Settings panel and show the dialog.
+     *
+     * @param dialog Dialog to show.
+     */
+    public final void showDialog(Dialog dialog) {
+        dialog.getWindow().getAttributes().token = mToken;
+        dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_QS_DIALOG);
+        dialog.show();
+        getQsTile().onShowDialog();
+    }
+
+    /**
      * Gets the {@link Tile} for this service.
      * <p/>
      * This tile may be used to get or set the current state for this
@@ -155,8 +172,8 @@
             }
 
             @Override
-            public void onClick() throws RemoteException {
-                mHandler.sendEmptyMessage(H.MSG_TILE_CLICKED);
+            public void onClick(IBinder wtoken) throws RemoteException {
+                mHandler.obtainMessage(H.MSG_TILE_CLICKED, wtoken).sendToTarget();
             }
         };
     }
@@ -185,19 +202,20 @@
                 case MSG_TILE_REMOVED:
                     TileService.this.onTileAdded();
                     break;
-                case MSG_START_LISTENING:
+                case MSG_STOP_LISTENING:
                     if (mListening) {
                         mListening = false;
                         TileService.this.onStopListening();
                     }
                     break;
-                case MSG_STOP_LISTENING:
+                case MSG_START_LISTENING:
                     if (!mListening) {
                         mListening = true;
                         TileService.this.onStartListening();
                     }
                     break;
                 case MSG_TILE_CLICKED:
+                    mToken = (IBinder) msg.obj;
                     TileService.this.onClick();
                     break;
             }
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 1521f2e..d6bc27c 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -238,6 +238,7 @@
             @ViewDebug.IntToString(from = TYPE_VOICE_INTERACTION, to = "TYPE_VOICE_INTERACTION"),
             @ViewDebug.IntToString(from = TYPE_VOICE_INTERACTION_STARTING, to = "TYPE_VOICE_INTERACTION_STARTING"),
             @ViewDebug.IntToString(from = TYPE_DOCK_DIVIDER, to = "TYPE_DOCK_DIVIDER"),
+            @ViewDebug.IntToString(from = TYPE_QS_DIALOG, to = "TYPE_QS_DIALOG"),
         })
         public int type;
 
@@ -584,6 +585,13 @@
         public static final int TYPE_DOCK_DIVIDER = FIRST_SYSTEM_WINDOW+34;
 
         /**
+         * Window type: like {@link #TYPE_APPLICATION_ATTACHED_DIALOG}, but used
+         * by Quick Settings Tiles.
+         * @hide
+         */
+        public static final int TYPE_QS_DIALOG = FIRST_SYSTEM_WINDOW+35;
+
+        /**
          * End of types of system windows.
          */
         public static final int LAST_SYSTEM_WINDOW      = 2999;
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 0473016f..8a90c4c 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -199,7 +199,6 @@
     external/sqlite/android \
     external/expat/lib \
     external/tremor/Tremor \
-    external/jpeg \
     external/harfbuzz_ng/src \
     libcore/include \
     $(call include-path-for, audio-utils) \
@@ -238,7 +237,7 @@
     libicuuc \
     libicui18n \
     libmedia \
-    libjpeg \
+    libjpeg-turbo \
     libusbhost \
     libharfbuzz_ng \
     libz \
diff --git a/core/jni/android/graphics/BitmapRegionDecoder.cpp b/core/jni/android/graphics/BitmapRegionDecoder.cpp
index 3cdf640..f10f4bd 100644
--- a/core/jni/android/graphics/BitmapRegionDecoder.cpp
+++ b/core/jni/android/graphics/BitmapRegionDecoder.cpp
@@ -16,17 +16,20 @@
 
 #define LOG_TAG "BitmapRegionDecoder"
 
-#include "AutoDecodeCancel.h"
 #include "BitmapFactory.h"
 #include "CreateJavaOutputStreamAdaptor.h"
-#include "SkBitmap.h"
-#include "SkData.h"
 #include "GraphicsJNI.h"
-#include "SkImageEncoder.h"
+#include "Utils.h"
+
+#include "SkBitmap.h"
+#include "SkBitmapRegionDecoder.h"
+#include "SkCodec.h"
+#include "SkData.h"
+#include "SkEncodedFormat.h"
 #include "SkUtils.h"
 #include "SkPixelRef.h"
 #include "SkStream.h"
-#include "Utils.h"
+
 #include "android_nio_utils.h"
 #include "android_util_Binder.h"
 #include "core_jni_helpers.h"
@@ -39,60 +42,54 @@
 
 using namespace android;
 
-class BitmapRegionDecoder {
-public:
-    BitmapRegionDecoder(SkImageDecoder* decoder, int width, int height) {
-        fDecoder = decoder;
-        fWidth = width;
-        fHeight = height;
-    }
-    ~BitmapRegionDecoder() {
-        delete fDecoder;
+// This is very similar to, and based on, getMimeTypeString() in BitmapFactory.
+jstring encodedFormatToString(JNIEnv* env, SkEncodedFormat format) {
+    const char* mimeType;
+    switch (format) {
+        case SkEncodedFormat::kBMP_SkEncodedFormat:
+            mimeType = "image/bmp";
+            break;
+        case SkEncodedFormat::kGIF_SkEncodedFormat:
+            mimeType = "image/gif";
+            break;
+        case SkEncodedFormat::kICO_SkEncodedFormat:
+            mimeType = "image/x-ico";
+            break;
+        case SkEncodedFormat::kJPEG_SkEncodedFormat:
+            mimeType = "image/jpeg";
+            break;
+        case SkEncodedFormat::kPNG_SkEncodedFormat:
+            mimeType = "image/png";
+            break;
+        case SkEncodedFormat::kWEBP_SkEncodedFormat:
+            mimeType = "image/webp";
+            break;
+        case SkEncodedFormat::kWBMP_SkEncodedFormat:
+            mimeType = "image/vnd.wap.wbmp";
+            break;
+        default:
+            mimeType = nullptr;
+            break;
     }
 
-    bool decodeRegion(SkBitmap* bitmap, const SkIRect& rect,
-                      SkColorType pref, int sampleSize) {
-        fDecoder->setSampleSize(sampleSize);
-        return fDecoder->decodeSubset(bitmap, rect, pref);
+    jstring jstr = nullptr;
+    if (mimeType != nullptr) {
+        jstr = env->NewStringUTF(mimeType);
     }
-
-    SkImageDecoder* getDecoder() const { return fDecoder; }
-    int getWidth() const { return fWidth; }
-    int getHeight() const { return fHeight; }
-
-private:
-    SkImageDecoder* fDecoder;
-    int fWidth;
-    int fHeight;
-};
+    return jstr;
+}
 
 // Takes ownership of the SkStreamRewindable. For consistency, deletes stream even
 // when returning null.
 static jobject createBitmapRegionDecoder(JNIEnv* env, SkStreamRewindable* stream) {
-    SkImageDecoder* decoder = SkImageDecoder::Factory(stream);
-    int width, height;
-    if (NULL == decoder) {
-        delete stream;
+    SkAutoTDelete<SkBitmapRegionDecoder> brd(
+            SkBitmapRegionDecoder::Create(stream, SkBitmapRegionDecoder::kAndroidCodec_Strategy));
+    if (NULL == brd) {
         doThrowIOE(env, "Image format not supported");
-        return nullObjectReturn("SkImageDecoder::Factory returned null");
+        return nullObjectReturn("CreateBitmapRegionDecoder returned null");
     }
 
-    JavaPixelAllocator *javaAllocator = new JavaPixelAllocator(env);
-    decoder->setAllocator(javaAllocator);
-    javaAllocator->unref();
-
-    // This call passes ownership of stream to the decoder, or deletes on failure.
-    if (!decoder->buildTileIndex(stream, &width, &height)) {
-        char msg[100];
-        snprintf(msg, sizeof(msg), "Image failed to decode using %s decoder",
-                decoder->getFormatName());
-        doThrowIOE(env, msg);
-        delete decoder;
-        return nullObjectReturn("decoder->buildTileIndex returned false");
-    }
-
-    BitmapRegionDecoder *bm = new BitmapRegionDecoder(decoder, width, height);
-    return GraphicsJNI::createBitmapRegionDecoder(env, bm);
+    return GraphicsJNI::createBitmapRegionDecoder(env, brd.detach());
 }
 
 static jobject nativeNewInstanceFromByteArray(JNIEnv* env, jobject, jbyteArray byteArray,
@@ -160,102 +157,106 @@
 
 /*
  * nine patch not supported
- *
  * purgeable not supported
  * reportSizeToVM not supported
  */
-static jobject nativeDecodeRegion(JNIEnv* env, jobject, jlong brdHandle,
-                                jint start_x, jint start_y, jint width, jint height, jobject options) {
-    BitmapRegionDecoder *brd = reinterpret_cast<BitmapRegionDecoder*>(brdHandle);
-    jobject tileBitmap = NULL;
-    SkImageDecoder *decoder = brd->getDecoder();
-    int sampleSize = 1;
-    SkColorType prefColorType = kUnknown_SkColorType;
-    bool doDither = true;
-    bool preferQualityOverSpeed = false;
-    bool requireUnpremultiplied = false;
+static jobject nativeDecodeRegion(JNIEnv* env, jobject, jlong brdHandle, jint inputX,
+        jint inputY, jint inputWidth, jint inputHeight, jobject options) {
 
+    // Set default options.
+    int sampleSize = 1;
+    SkColorType colorType = kN32_SkColorType;
+    bool requireUnpremul = false;
+    jobject javaBitmap = NULL;
+
+    // Update the default options with any options supplied by the client.
     if (NULL != options) {
         sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID);
-        // initialize these, in case we fail later on
+        jobject jconfig = env->GetObjectField(options, gOptions_configFieldID);
+        colorType = GraphicsJNI::getNativeBitmapColorType(env, jconfig);
+        if (kAlpha_8_SkColorType == colorType) {
+            colorType = kGray_8_SkColorType;
+        }
+        requireUnpremul = !env->GetBooleanField(options, gOptions_premultipliedFieldID);
+        javaBitmap = env->GetObjectField(options, gOptions_bitmapFieldID);
+        // The Java options of ditherMode and preferQualityOverSpeed are deprecated.  We will
+        // ignore the values of these fields.
+
+        // Initialize these fields to indicate a failure.  If the decode succeeds, we
+        // will update them later on.
         env->SetIntField(options, gOptions_widthFieldID, -1);
         env->SetIntField(options, gOptions_heightFieldID, -1);
         env->SetObjectField(options, gOptions_mimeFieldID, 0);
-
-        jobject jconfig = env->GetObjectField(options, gOptions_configFieldID);
-        prefColorType = GraphicsJNI::getNativeBitmapColorType(env, jconfig);
-        doDither = env->GetBooleanField(options, gOptions_ditherFieldID);
-        preferQualityOverSpeed = env->GetBooleanField(options,
-                gOptions_preferQualityOverSpeedFieldID);
-        // Get the bitmap for re-use if it exists.
-        tileBitmap = env->GetObjectField(options, gOptions_bitmapFieldID);
-        requireUnpremultiplied = !env->GetBooleanField(options, gOptions_premultipliedFieldID);
     }
 
-    decoder->setDitherImage(doDither);
-    decoder->setPreferQualityOverSpeed(preferQualityOverSpeed);
-    decoder->setRequireUnpremultipliedColors(requireUnpremultiplied);
-    AutoDecoderCancel adc(options, decoder);
-
-    // To fix the race condition in case "requestCancelDecode"
-    // happens earlier than AutoDecoderCancel object is added
-    // to the gAutoDecoderCancelMutex linked list.
-    if (NULL != options && env->GetBooleanField(options, gOptions_mCancelID)) {
-        return nullObjectReturn("gOptions_mCancelID");;
+    // Recycle a bitmap if possible.
+    android::Bitmap* recycledBitmap = nullptr;
+    size_t recycledBytes = 0;
+    if (javaBitmap) {
+        recycledBitmap = GraphicsJNI::getBitmap(env, javaBitmap);
+        if (recycledBitmap->peekAtPixelRef()->isImmutable()) {
+            ALOGW("Warning: Reusing an immutable bitmap as an image decoder target.");
+        }
+        recycledBytes = GraphicsJNI::getBitmapAllocationByteCount(env, javaBitmap);
     }
 
-    SkIRect region;
-    region.fLeft = start_x;
-    region.fTop = start_y;
-    region.fRight = start_x + width;
-    region.fBottom = start_y + height;
+    // Set up the pixel allocator
+    SkBRDAllocator* allocator = nullptr;
+    RecyclingClippingPixelAllocator recycleAlloc(recycledBitmap, recycledBytes);
+    JavaPixelAllocator javaAlloc(env);
+    if (javaBitmap) {
+        allocator = &recycleAlloc;
+        // We are required to match the color type of the recycled bitmap.
+        colorType = recycledBitmap->info().colorType();
+    } else {
+        allocator = &javaAlloc;
+    }
+
+    // Decode the region.
+    SkIRect subset = SkIRect::MakeXYWH(inputX, inputY, inputWidth, inputHeight);
+    SkBitmapRegionDecoder* brd =
+            reinterpret_cast<SkBitmapRegionDecoder*>(brdHandle);
     SkBitmap bitmap;
-
-    if (tileBitmap != NULL) {
-        // Re-use bitmap.
-        GraphicsJNI::getSkBitmap(env, tileBitmap, &bitmap);
+    if (!brd->decodeRegion(&bitmap, allocator, subset, sampleSize, colorType, requireUnpremul)) {
+        return nullObjectReturn("Failed to decode region.");
     }
 
-    if (!brd->decodeRegion(&bitmap, region, prefColorType, sampleSize)) {
-        return nullObjectReturn("decoder->decodeRegion returned false");
-    }
-
-    // update options (if any)
+    // If the client provided options, indicate that the decode was successful.
     if (NULL != options) {
         env->SetIntField(options, gOptions_widthFieldID, bitmap.width());
         env->SetIntField(options, gOptions_heightFieldID, bitmap.height());
-        // TODO: set the mimeType field with the data from the codec.
-        // but how to reuse a set of strings, rather than allocating new one
-        // each time?
         env->SetObjectField(options, gOptions_mimeFieldID,
-                            getMimeTypeString(env, decoder->getFormat()));
+                encodedFormatToString(env, brd->getEncodedFormat()));
     }
 
-    if (tileBitmap != NULL) {
-        bitmap.notifyPixelsChanged();
-        return tileBitmap;
+    // If we may have reused a bitmap, we need to indicate that the pixels have changed.
+    if (javaBitmap) {
+        recycleAlloc.copyIfNecessary();
+        return javaBitmap;
     }
 
-    JavaPixelAllocator* allocator = (JavaPixelAllocator*) decoder->getAllocator();
-
     int bitmapCreateFlags = 0;
-    if (!requireUnpremultiplied) bitmapCreateFlags |= GraphicsJNI::kBitmapCreateFlag_Premultiplied;
-    return GraphicsJNI::createBitmap(env, allocator->getStorageObjAndReset(),
-            bitmapCreateFlags);
+    if (!requireUnpremul) {
+        bitmapCreateFlags |= GraphicsJNI::kBitmapCreateFlag_Premultiplied;
+    }
+    return GraphicsJNI::createBitmap(env, javaAlloc.getStorageObjAndReset(), bitmapCreateFlags);
 }
 
 static jint nativeGetHeight(JNIEnv* env, jobject, jlong brdHandle) {
-    BitmapRegionDecoder *brd = reinterpret_cast<BitmapRegionDecoder*>(brdHandle);
-    return static_cast<jint>(brd->getHeight());
+    SkBitmapRegionDecoder* brd =
+            reinterpret_cast<SkBitmapRegionDecoder*>(brdHandle);
+    return static_cast<jint>(brd->height());
 }
 
 static jint nativeGetWidth(JNIEnv* env, jobject, jlong brdHandle) {
-    BitmapRegionDecoder *brd = reinterpret_cast<BitmapRegionDecoder*>(brdHandle);
-    return static_cast<jint>(brd->getWidth());
+    SkBitmapRegionDecoder* brd =
+            reinterpret_cast<SkBitmapRegionDecoder*>(brdHandle);
+    return static_cast<jint>(brd->width());
 }
 
 static void nativeClean(JNIEnv* env, jobject, jlong brdHandle) {
-    BitmapRegionDecoder *brd = reinterpret_cast<BitmapRegionDecoder*>(brdHandle);
+    SkBitmapRegionDecoder* brd =
+            reinterpret_cast<SkBitmapRegionDecoder*>(brdHandle);
     delete brd;
 }
 
diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp
index ed44019..3d5091a 100644
--- a/core/jni/android/graphics/Graphics.cpp
+++ b/core/jni/android/graphics/Graphics.cpp
@@ -439,7 +439,7 @@
     return env->CallIntMethod(javaBitmap, gBitmap_getAllocationByteCountMethodID);
 }
 
-jobject GraphicsJNI::createBitmapRegionDecoder(JNIEnv* env, BitmapRegionDecoder* bitmap)
+jobject GraphicsJNI::createBitmapRegionDecoder(JNIEnv* env, SkBitmapRegionDecoder* bitmap)
 {
     SkASSERT(bitmap != NULL);
 
@@ -677,6 +677,91 @@
 
 ////////////////////////////////////////////////////////////////////////////////
 
+RecyclingClippingPixelAllocator::RecyclingClippingPixelAllocator(
+        android::Bitmap* recycledBitmap, size_t recycledBytes)
+    : mRecycledBitmap(recycledBitmap)
+    , mRecycledBytes(recycledBytes)
+    , mSkiaBitmap(nullptr)
+    , mNeedsCopy(false)
+{}
+
+RecyclingClippingPixelAllocator::~RecyclingClippingPixelAllocator() {}
+
+bool RecyclingClippingPixelAllocator::allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) {
+    // Ensure that the caller did not pass in a NULL bitmap to the constructor or this
+    // function.
+    LOG_ALWAYS_FATAL_IF(!mRecycledBitmap);
+    LOG_ALWAYS_FATAL_IF(!bitmap);
+    mSkiaBitmap = bitmap;
+
+    // This behaves differently than the RecyclingPixelAllocator.  For backwards
+    // compatibility, the original color type of the recycled bitmap must be maintained.
+    if (mRecycledBitmap->info().colorType() != bitmap->colorType()) {
+        return false;
+    }
+
+    // The Skia bitmap specifies the width and height needed by the decoder.
+    // mRecycledBitmap specifies the width and height of the bitmap that we
+    // want to reuse.  Neither can be changed.  We will try to find a way
+    // to reuse the memory.
+    const int maxWidth = SkTMax(bitmap->width(), mRecycledBitmap->info().width());
+    const int maxHeight = SkTMax(bitmap->height(), mRecycledBitmap->info().height());
+    const SkImageInfo maxInfo = bitmap->info().makeWH(maxWidth, maxHeight);
+    const size_t rowBytes = maxInfo.minRowBytes();
+    const size_t bytesNeeded = maxInfo.getSafeSize(rowBytes);
+    if (bytesNeeded <= mRecycledBytes) {
+        // Here we take advantage of reconfigure() to reset the rowBytes and ctable
+        // of mRecycledBitmap.  It is very important that we pass in
+        // mRecycledBitmap->info() for the SkImageInfo.  According to the
+        // specification for BitmapRegionDecoder, we are not allowed to change
+        // the SkImageInfo.
+        mRecycledBitmap->reconfigure(mRecycledBitmap->info(), rowBytes, ctable);
+
+        // This call will give the bitmap the same pixelRef as mRecycledBitmap.
+        bitmap->setPixelRef(mRecycledBitmap->refPixelRef())->unref();
+
+        // Make sure that the recycled bitmap has the correct alpha type.
+        mRecycledBitmap->setAlphaType(bitmap->alphaType());
+
+        bitmap->lockPixels();
+        mNeedsCopy = false;
+
+        // TODO: If the dimensions of the SkBitmap are smaller than those of
+        // mRecycledBitmap, should we zero the memory in mRecycledBitmap?
+        return true;
+    }
+
+    // In the event that mRecycledBitmap is not large enough, allocate new memory
+    // on the heap.
+    SkBitmap::HeapAllocator heapAllocator;
+
+    // We will need to copy from heap memory to mRecycledBitmap's memory after the
+    // decode is complete.
+    mNeedsCopy = true;
+
+    return heapAllocator.allocPixelRef(bitmap, ctable);
+}
+
+void RecyclingClippingPixelAllocator::copyIfNecessary() {
+    if (mNeedsCopy) {
+        SkPixelRef* recycledPixels = mRecycledBitmap->refPixelRef();
+        void* dst = recycledPixels->pixels();
+        size_t dstRowBytes = mRecycledBitmap->rowBytes();
+        size_t bytesToCopy = SkTMin(mRecycledBitmap->info().minRowBytes(),
+                mSkiaBitmap->info().minRowBytes());
+        for (int y = 0; y < mRecycledBitmap->info().height(); y++) {
+            memcpy(dst, mSkiaBitmap->getAddr(0, y), bytesToCopy);
+            dst = SkTAddOffset<void>(dst, dstRowBytes);
+        }
+        recycledPixels->notifyPixelsChanged();
+        recycledPixels->unref();
+    }
+    mRecycledBitmap = nullptr;
+    mSkiaBitmap = nullptr;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
 AshmemPixelAllocator::AshmemPixelAllocator(JNIEnv *env) {
     LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&mJavaVM) != JNI_OK,
             "env->GetJavaVM failed");
diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h
index 90f8291..e99a3ff 100644
--- a/core/jni/android/graphics/GraphicsJNI.h
+++ b/core/jni/android/graphics/GraphicsJNI.h
@@ -3,6 +3,8 @@
 
 #include "Bitmap.h"
 #include "SkBitmap.h"
+#include "SkBRDAllocator.h"
+#include "SkCodec.h"
 #include "SkDevice.h"
 #include "SkPixelRef.h"
 #include "SkMallocPixelRef.h"
@@ -12,7 +14,7 @@
 #include <Canvas.h>
 #include <jni.h>
 
-class BitmapRegionDecoder;
+class SkBitmapRegionDecoder;
 class SkCanvas;
 
 namespace android {
@@ -90,7 +92,7 @@
 
     static jobject createRegion(JNIEnv* env, SkRegion* region);
 
-    static jobject createBitmapRegionDecoder(JNIEnv* env, BitmapRegionDecoder* bitmap);
+    static jobject createBitmapRegionDecoder(JNIEnv* env, SkBitmapRegionDecoder* bitmap);
 
     static android::Bitmap* allocateJavaPixelRef(JNIEnv* env, SkBitmap* bitmap,
             SkColorTable* ctable);
@@ -123,7 +125,7 @@
  *  ensure that the allocated buffer is properly accounted for with a
  *  reference in the heap (or a JNI global reference).
  */
-class JavaPixelAllocator : public SkBitmap::Allocator {
+class JavaPixelAllocator : public SkBRDAllocator {
 public:
     JavaPixelAllocator(JNIEnv* env);
     ~JavaPixelAllocator();
@@ -139,11 +141,78 @@
         return result;
     };
 
+    /**
+     *  Indicates that this allocator allocates zero initialized
+     *  memory.
+     */
+    SkCodec::ZeroInitialized zeroInit() const override { return SkCodec::kYes_ZeroInitialized; }
+
 private:
     JavaVM* mJavaVM;
     android::Bitmap* mStorage = nullptr;
 };
 
+/**
+ *  Allocator to handle reusing bitmaps for BitmapRegionDecoder.
+ *
+ *  The BitmapRegionDecoder documentation states that, if it is
+ *  provided, the recycled bitmap will always be reused, clipping
+ *  the decoded output to fit in the recycled bitmap if necessary.
+ *  This allocator implements that behavior.
+ *
+ *  Skia's SkBitmapRegionDecoder expects the memory that
+ *  is allocated to be large enough to decode the entire region
+ *  that is requested.  It will decode directly into the memory
+ *  that is provided.
+ *
+ *  FIXME: BUG:25465958
+ *  If the recycled bitmap is not large enough for the decode
+ *  requested, meaning that a clip is required, we will allocate
+ *  enough memory for Skia to perform the decode, and then copy
+ *  from the decoded output into the recycled bitmap.
+ *
+ *  If the recycled bitmap is large enough for the decode requested,
+ *  we will provide that memory for Skia to decode directly into.
+ *
+ *  This allocator should only be used for a single allocation.
+ *  After we reuse the recycledBitmap once, it is dangerous to
+ *  reuse it again, given that it still may be in use from our
+ *  first allocation.
+ */
+class RecyclingClippingPixelAllocator : public SkBRDAllocator {
+public:
+
+    RecyclingClippingPixelAllocator(android::Bitmap* recycledBitmap,
+            size_t recycledBytes);
+
+    ~RecyclingClippingPixelAllocator();
+
+    virtual bool allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) override;
+
+    /**
+     *  Must be called!
+     *
+     *  In the event that the recycled bitmap is not large enough for
+     *  the allocation requested, we will allocate memory on the heap
+     *  instead.  As a final step, once we are done using this memory,
+     *  we will copy the contents of the heap memory into the recycled
+     *  bitmap's memory, clipping as necessary.
+     */
+    void copyIfNecessary();
+
+    /**
+     *  Indicates that this allocator does not allocate zero initialized
+     *  memory.
+     */
+    SkCodec::ZeroInitialized zeroInit() const override { return SkCodec::kNo_ZeroInitialized; }
+
+private:
+    android::Bitmap* mRecycledBitmap;
+    const size_t     mRecycledBytes;
+    SkBitmap*        mSkiaBitmap;
+    bool             mNeedsCopy;
+};
+
 class AshmemPixelAllocator : public SkBitmap::Allocator {
 public:
     AshmemPixelAllocator(JNIEnv* env);
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index c9a5f8f..e6fe447 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -116,7 +116,7 @@
 
     final HandlerThread mThread;
     final BackgroundHandler mBackgroundHandler;
-    final MainHandler mMainHandler = new MainHandler();
+    final MainHandler mMainHandler = new MainHandler(Looper.getMainLooper());
 
     private ApplicationsState(Application app) {
         mContext = app;
@@ -687,6 +687,10 @@
         static final int MSG_LAUNCHER_INFO_CHANGED = 7;
         static final int MSG_LOAD_ENTRIES_COMPLETE = 8;
 
+        public MainHandler(Looper looper) {
+            super(looper);
+        }
+
         @Override
         public void handleMessage(Message msg) {
             rebuildActiveSessions();
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
index 581c810..102e47a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
@@ -77,10 +77,7 @@
             mDrawerLayout = null;
             return;
         }
-        if (sDashboardCategories == null) {
-            sTileCache = new HashMap<>();
-            sDashboardCategories = TileUtils.getCategories(this, sTileCache);
-        }
+        getDashboardCategories();
         setActionBar(toolbar);
         mDrawerAdapter = new SettingsDrawerAdapter(this);
         ListView listView = (ListView) findViewById(R.id.left_drawer);
@@ -109,19 +106,23 @@
     protected void onResume() {
         super.onResume();
 
-        final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
-        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
-        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
-        filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
-        filter.addDataScheme("package");
-        registerReceiver(mPackageReceiver, filter);
+        if (mDrawerLayout != null) {
+            final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+            filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+            filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+            filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+            filter.addDataScheme("package");
+            registerReceiver(mPackageReceiver, filter);
 
-        new CategoriesUpdater().execute();
+            new CategoriesUpdater().execute();
+        }
     }
 
     @Override
     protected void onPause() {
-        unregisterReceiver(mPackageReceiver);
+        if (mDrawerLayout != null) {
+            unregisterReceiver(mPackageReceiver);
+        }
 
         super.onPause();
     }
@@ -178,6 +179,10 @@
     }
 
     public List<DashboardCategory> getDashboardCategories() {
+        if (sDashboardCategories == null) {
+            sTileCache = new HashMap<>();
+            sDashboardCategories = TileUtils.getCategories(this, sTileCache);
+        }
         return sDashboardCategories;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileServiceWrapper.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileServiceWrapper.java
index 55f4736..a5e1fd5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileServiceWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileServiceWrapper.java
@@ -66,9 +66,9 @@
     }
 
     @Override
-    public void onClick() {
+    public void onClick(IBinder token) {
         try {
-            mService.onClick();
+            mService.onClick(token);
         } catch (Exception e) {
             Log.d(TAG, "Caught exception from QSTileService", e);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomTile.java
index d26e8d6..04006eb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomTile.java
@@ -24,12 +24,16 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
 import android.graphics.drawable.Drawable;
+import android.os.Binder;
 import android.os.IBinder;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.service.quicksettings.IQSTileService;
 import android.service.quicksettings.Tile;
 import android.util.Log;
-
+import android.view.IWindowManager;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
 import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.qs.QSTile;
 import com.android.systemui.qs.QSTileServiceWrapper;
@@ -38,19 +42,26 @@
 public class CustomTile extends QSTile<QSTile.State> {
     public static final String PREFIX = "custom(";
 
+    private static final boolean DEBUG = false;
+
     // We don't want to thrash binding and unbinding if the user opens and closes the panel a lot.
     // So instead we have a period of waiting.
     private static final long UNBIND_DELAY = 30000;
 
     private final ComponentName mComponent;
     private final Tile mTile;
+    private final IWindowManager mWindowManager;
+    private final IBinder mToken = new Binder();
 
     private QSTileServiceWrapper mService;
     private boolean mListening;
     private boolean mBound;
+    private boolean mIsTokenGranted;
+    private boolean mIsShowingDialog;
 
     private CustomTile(QSTileHost host, String action) {
         super(host);
+        mWindowManager = WindowManagerGlobal.getWindowManagerService();
         mComponent = ComponentName.unflattenFromString(action);
         mTile = new Tile(mComponent, host);
         try {
@@ -72,12 +83,15 @@
     }
 
     public void updateState(Tile tile) {
-        Log.d("TileService", "Setting state " + tile.getLabel());
         mTile.setIcon(tile.getIcon());
         mTile.setLabel(tile.getLabel());
         mTile.setContentDescription(tile.getContentDescription());
     }
 
+    public void onDialogShown() {
+        mIsShowingDialog = true;
+    }
+
     @Override
     public void setListening(boolean listening) {
         if (mListening == listening) return;
@@ -95,14 +109,30 @@
             if (mService!= null) {
                 mService.onStopListening();
             }
+            if (mIsTokenGranted && !mIsShowingDialog) {
+                try {
+                    if (DEBUG) Log.d(TAG, "Removing token");
+                    mWindowManager.removeWindowToken(mToken);
+                } catch (RemoteException e) {
+                }
+                mIsTokenGranted = false;
+            }
+            mIsShowingDialog = false;
             mHandler.postDelayed(mUnbind, UNBIND_DELAY);
         }
     }
-    
+
     @Override
     protected void handleDestroy() {
         super.handleDestroy();
         mHandler.removeCallbacks(mUnbind);
+        if (mIsTokenGranted) {
+            try {
+                if (DEBUG) Log.d(TAG, "Removing token");
+                mWindowManager.removeWindowToken(mToken);
+            } catch (RemoteException e) {
+            }
+        }
         mUnbind.run();
     }
 
@@ -119,7 +149,13 @@
     @Override
     protected void handleClick() {
         if (mService != null) {
-            mService.onClick();
+            try {
+                if (DEBUG) Log.d(TAG, "Adding token");
+                mWindowManager.addWindowToken(mToken, WindowManager.LayoutParams.TYPE_QS_DIALOG);
+                mIsTokenGranted = true;
+            } catch (RemoteException e) {
+            }
+            mService.onClick(mToken);
         } else {
             Log.e(TAG, "Click with no service " + getTileSpec());
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
index 57c2648..f7ff8aa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
@@ -293,12 +293,21 @@
         verifyCaller(tile.getComponentName().getPackageName());
         CustomTile customTile = getTileForComponent(tile.getComponentName());
         if (customTile != null) {
-            Log.d("TileService", "Got tile update for " + tile.getComponentName());
             customTile.updateState(tile);
             customTile.refreshState();
         }
     }
 
+    @Override
+    public void onShowDialog(Tile tile) throws RemoteException {
+        verifyCaller(tile.getComponentName().getPackageName());
+        CustomTile customTile = getTileForComponent(tile.getComponentName());
+        if (customTile != null) {
+            customTile.onDialogShown();
+            collapsePanels();
+        }
+    }
+
     private void verifyCaller(String packageName) {
         try {
             int uid = mContext.getPackageManager().getPackageUid(packageName,
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 639753a..9a7d153 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -108,9 +108,6 @@
 import android.view.KeyCharacterMap.FallbackAction;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.policy.PhoneWindow;
 import android.view.Surface;
 import android.view.View;
 import android.view.ViewConfiguration;
@@ -124,6 +121,8 @@
 import android.view.animation.AnimationSet;
 import android.view.animation.AnimationUtils;
 import com.android.internal.R;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.policy.PhoneWindow;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.util.ScreenShapeHelper;
 import com.android.internal.widget.PointerLocationView;
@@ -1915,6 +1914,7 @@
             case TYPE_PRIVATE_PRESENTATION:
             case TYPE_VOICE_INTERACTION:
             case TYPE_ACCESSIBILITY_OVERLAY:
+            case TYPE_QS_DIALOG:
                 // The window manager will check these.
                 break;
             case TYPE_PHONE:
@@ -2110,6 +2110,8 @@
             return 2;
         case TYPE_DOCK_DIVIDER:
             return 2;
+        case TYPE_QS_DIALOG:
+            return 2;
         case TYPE_PHONE:
             return 3;
         case TYPE_SEARCH_BAR:
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index c246609..a4b4276 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -351,6 +351,7 @@
                         case WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG:
                         case WindowManager.LayoutParams.TYPE_SYSTEM_ERROR:
                         case WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY:
+                        case WindowManager.LayoutParams.TYPE_QS_DIALOG:
                         case WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL: {
                             Rect magnifiedRegionBounds = mTempRect2;
                             mMagnifedViewport.getMagnifiedFrameInContentCoordsLocked(
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 707450c..f153873 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -43,6 +43,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
 import static android.view.WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION;
+import static android.view.WindowManager.LayoutParams.TYPE_QS_DIALOG;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
@@ -1865,6 +1866,11 @@
                           + attrs.token + ".  Aborting.");
                     return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                 }
+                if (type == TYPE_QS_DIALOG) {
+                    Slog.w(TAG, "Attempted to add QS dialog window with unknown token "
+                          + attrs.token + ".  Aborting.");
+                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
+                }
                 if (type == TYPE_ACCESSIBILITY_OVERLAY) {
                     Slog.w(TAG, "Attempted to add Accessibility overlay window with unknown token "
                             + attrs.token + ".  Aborting.");