Merge "Print spooler UI polish and bug fixes." into lmp-dev
diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java
index 9dc9766..47f72a8 100644
--- a/core/java/android/view/RenderNode.java
+++ b/core/java/android/view/RenderNode.java
@@ -367,6 +367,10 @@
         throw new IllegalArgumentException("Unrecognized outline?");
     }
 
+    public boolean hasShadow() {
+        return nHasShadow(mNativeRenderNode);
+    }
+
     /**
      * Enables or disables clipping to the outline.
      *
@@ -861,6 +865,7 @@
             float alpha);
     private static native boolean nSetOutlineEmpty(long renderNode);
     private static native boolean nSetOutlineNone(long renderNode);
+    private static native boolean nHasShadow(long renderNode);
     private static native boolean nSetClipToOutline(long renderNode, boolean clipToOutline);
     private static native boolean nSetRevealClip(long renderNode,
             boolean shouldClip, float x, float y, float radius);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 92ad4e8..770e78c 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -10224,6 +10224,7 @@
      *
      * @return true if the content in this view might overlap, false otherwise.
      */
+    @ViewDebug.ExportedProperty(category = "drawing")
     public boolean hasOverlappingRendering() {
         return true;
     }
@@ -10926,6 +10927,17 @@
         invalidateViewProperty(false, false);
     }
 
+    /**
+     * HierarchyViewer only
+     *
+     * @hide
+     */
+    @ViewDebug.ExportedProperty(category = "drawing")
+    public boolean hasShadow() {
+        return mRenderNode.hasShadow();
+    }
+
+
     /** @hide */
     public void setRevealClip(boolean shouldClip, float x, float y, float radius) {
         mRenderNode.setRevealClip(shouldClip, x, y, radius);
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 974fe4e..ac1b6a9 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -3301,6 +3301,7 @@
      * @return True if the group's children will be clipped to their bounds,
      * false otherwise.
      */
+    @ViewDebug.ExportedProperty(category = "drawing")
     public boolean getClipChildren() {
         return ((mGroupFlags & FLAG_CLIP_CHILDREN) != 0);
     }
@@ -3349,6 +3350,7 @@
      *
      * @attr ref android.R.styleable#ViewGroup_clipToPadding
      */
+    @ViewDebug.ExportedProperty(category = "drawing")
     public boolean getClipToPadding() {
         return hasBooleanFlag(FLAG_CLIP_TO_PADDING);
     }
diff --git a/core/jni/android_view_RenderNode.cpp b/core/jni/android_view_RenderNode.cpp
index 949f4ff..050037e 100644
--- a/core/jni/android_view_RenderNode.cpp
+++ b/core/jni/android_view_RenderNode.cpp
@@ -173,6 +173,12 @@
     return true;
 }
 
+static jboolean android_view_RenderNode_hasShadow(JNIEnv* env,
+        jobject clazz, jlong renderNodePtr) {
+    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+    return renderNode->stagingProperties().hasShadow();
+}
+
 static jboolean android_view_RenderNode_setClipToOutline(JNIEnv* env,
         jobject clazz, jlong renderNodePtr, jboolean clipToOutline) {
     RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
@@ -491,6 +497,7 @@
     { "nSetOutlineConvexPath", "(JJF)Z", (void*) android_view_RenderNode_setOutlineConvexPath },
     { "nSetOutlineEmpty",      "(J)Z",   (void*) android_view_RenderNode_setOutlineEmpty },
     { "nSetOutlineNone",       "(J)Z",   (void*) android_view_RenderNode_setOutlineNone },
+    { "nHasShadow",            "(J)Z",   (void*) android_view_RenderNode_hasShadow },
     { "nSetClipToOutline",     "(JZ)Z",  (void*) android_view_RenderNode_setClipToOutline },
     { "nSetRevealClip",        "(JZFFF)Z", (void*) android_view_RenderNode_setRevealClip },
 
diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h
index 0c8d07f..46eeb6a 100644
--- a/libs/hwui/RenderProperties.h
+++ b/libs/hwui/RenderProperties.h
@@ -569,6 +569,10 @@
         return getClipToBounds() && (getZ() <= 0 || getOutline().isEmpty());
     }
 
+    bool hasShadow() const {
+        return getZ() >= 0.0f && getOutline().getPath() != NULL;
+    }
+
 private:
     // Rendering properties
     struct PrimitiveFields {
diff --git a/libs/hwui/RenderState.cpp b/libs/hwui/RenderState.cpp
index a7c5e85..ec8307f 100644
--- a/libs/hwui/RenderState.cpp
+++ b/libs/hwui/RenderState.cpp
@@ -38,6 +38,7 @@
 }
 
 void RenderState::onGLContextDestroyed() {
+    AutoMutex _lock(mLayerLock);
     if (CC_UNLIKELY(!mActiveLayers.empty())) {
         mCaches->dumpMemoryUsage();
         for (std::set<renderthread::CanvasContext*>::iterator cit = mRegisteredContexts.begin();
@@ -51,6 +52,13 @@
             }
             context->mRootRenderNode->debugDumpLayers("  ");
         }
+
+        for (std::set<const Layer*>::iterator lit = mActiveLayers.begin();
+             lit != mActiveLayers.end(); lit++) {
+            const Layer* layer = *(lit);
+            ALOGD("Layer %p, fbo %d, buildlayered %d",
+                    layer, layer->getFbo(), layer->wasBuildLayered);
+        }
         LOG_ALWAYS_FATAL("layers have survived gl context destruction");
     }
 }
diff --git a/libs/hwui/RenderState.h b/libs/hwui/RenderState.h
index c7ab197..74bafca 100644
--- a/libs/hwui/RenderState.h
+++ b/libs/hwui/RenderState.h
@@ -19,6 +19,7 @@
 #include <set>
 #include <GLES2/gl2.h>
 #include <GLES2/gl2ext.h>
+#include <utils/Mutex.h>
 
 #include <private/hwui/DrawGlInfo.h>
 
@@ -52,9 +53,11 @@
     void debugOverdraw(bool enable, bool clear);
 
     void registerLayer(const Layer* layer) {
+        AutoMutex _lock(mLayerLock);
         mActiveLayers.insert(layer);
     }
     void unregisterLayer(const Layer* layer) {
+        AutoMutex _lock(mLayerLock);
         mActiveLayers.erase(layer);
     }
 
@@ -83,6 +86,7 @@
     GLsizei mViewportWidth;
     GLsizei mViewportHeight;
     GLuint mFramebuffer;
+    Mutex mLayerLock;
 };
 
 } /* namespace uirenderer */
diff --git a/services/core/java/com/android/server/NativeDaemonEvent.java b/services/core/java/com/android/server/NativeDaemonEvent.java
index 2095152..59d50bd 100644
--- a/services/core/java/com/android/server/NativeDaemonEvent.java
+++ b/services/core/java/com/android/server/NativeDaemonEvent.java
@@ -201,20 +201,16 @@
         }
         while (current < length) {
             // find the end of the word
-            if (quoted) {
-                wordEnd = current;
-                while ((wordEnd = rawEvent.indexOf('\"', wordEnd)) != -1) {
-                    if (rawEvent.charAt(wordEnd - 1) != '\\') {
-                        break;
-                    } else {
-                        wordEnd++; // skip this escaped quote and keep looking
-                    }
+            char terminator = quoted ? '\"' : ' ';
+            wordEnd = current;
+            while (wordEnd < length && rawEvent.charAt(wordEnd) != terminator) {
+                if (rawEvent.charAt(wordEnd) == '\\') {
+                    // skip the escaped char
+                    ++wordEnd;
                 }
-            } else {
-                wordEnd = rawEvent.indexOf(' ', current);
+                ++wordEnd;
             }
-            // if we didn't find the end-o-word token, take the rest of the string
-            if (wordEnd == -1) wordEnd = length;
+            if (wordEnd > length) wordEnd = length;
             String word = rawEvent.substring(current, wordEnd);
             current += word.length();
             if (!quoted) {
diff --git a/services/core/java/com/android/server/NsdService.java b/services/core/java/com/android/server/NsdService.java
index cf7e65c..cb1748d 100644
--- a/services/core/java/com/android/server/NsdService.java
+++ b/services/core/java/com/android/server/NsdService.java
@@ -397,8 +397,7 @@
                         break;
                     case NsdManager.NATIVE_DAEMON_EVENT:
                         NativeEvent event = (NativeEvent) msg.obj;
-                        if (!handleNativeEvent(event.code, event.raw,
-                                NativeDaemonEvent.unescapeArgs(event.raw))) {
+                        if (!handleNativeEvent(event.code, event.raw, event.cooked)) {
                             result = NOT_HANDLED;
                         }
                         break;
@@ -474,8 +473,14 @@
                     case NativeResponseCode.SERVICE_RESOLVED:
                         /* NNN resolveId fullName hostName port txtlen txtdata */
                         if (DBG) Slog.d(TAG, "SERVICE_RESOLVED Raw: " + raw);
-                        int index = cooked[2].indexOf(".");
-                        if (index == -1) {
+                        int index = 0;
+                        while (index < cooked[2].length() && cooked[2].charAt(index) != '.') {
+                            if (cooked[2].charAt(index) == '\\') {
+                                ++index;
+                            }
+                            ++index;
+                        }
+                        if (index >= cooked[2].length()) {
                             Slog.e(TAG, "Invalid service found " + raw);
                             break;
                         }
@@ -483,6 +488,8 @@
                         String rest = cooked[2].substring(index);
                         String type = rest.replace(".local.", "");
 
+                        name = unescape(name);
+
                         clientInfo.mResolvedService.setServiceName(name);
                         clientInfo.mResolvedService.setServiceType(type);
                         clientInfo.mResolvedService.setPort(Integer.parseInt(cooked[4]));
@@ -541,6 +548,30 @@
        }
     }
 
+    private String unescape(String s) {
+        StringBuilder sb = new StringBuilder(s.length());
+        for (int i = 0; i < s.length(); ++i) {
+            char c = s.charAt(i);
+            if (c == '\\') {
+                if (++i >= s.length()) {
+                    Slog.e(TAG, "Unexpected end of escape sequence in: " + s);
+                    break;
+                }
+                c = s.charAt(i);
+                if (c != '.' && c != '\\') {
+                    if (i + 2 >= s.length()) {
+                        Slog.e(TAG, "Unexpected end of escape sequence in: " + s);
+                        break;
+                    }
+                    c = (char) ((c-'0') * 100 + (s.charAt(i+1)-'0') * 10 + (s.charAt(i+2)-'0'));
+                    i += 2;
+                }
+            }
+            sb.append(c);
+        }
+        return sb.toString();
+    }
+
     private NativeDaemonConnector mNativeConnector;
     private final CountDownLatch mNativeDaemonConnected = new CountDownLatch(1);
 
@@ -625,10 +656,12 @@
     private class NativeEvent {
         final int code;
         final String raw;
+        final String[] cooked;
 
-        NativeEvent(int code, String raw) {
+        NativeEvent(int code, String raw, String[] cooked) {
             this.code = code;
             this.raw = raw;
+            this.cooked = cooked;
         }
     }
 
@@ -644,7 +677,7 @@
         public boolean onEvent(int code, String raw, String[] cooked) {
             // TODO: NDC translates a message to a callback, we could enhance NDC to
             // directly interact with a state machine through messages
-            NativeEvent event = new NativeEvent(code, raw);
+            NativeEvent event = new NativeEvent(code, raw, cooked);
             mNsdStateMachine.sendMessage(NsdManager.NATIVE_DAEMON_EVENT, event);
             return true;
         }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index aea00da..7b4270b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -3783,21 +3783,25 @@
         } else {
             pi = generatePackageInfoFromSettingsLPw(ps.name, flags, userId);
         }
-        if ((flags&PackageManager.GET_PERMISSIONS) == 0) {
-            if (numMatch == permissions.length) {
-                pi.requestedPermissions = permissions;
-            } else {
-                pi.requestedPermissions = new String[numMatch];
-                numMatch = 0;
-                for (int i=0; i<permissions.length; i++) {
-                    if (tmp[i]) {
-                        pi.requestedPermissions[numMatch] = permissions[i];
-                        numMatch++;
+        // The above might return null in cases of uninstalled apps or install-state
+        // skew across users/profiles.
+        if (pi != null) {
+            if ((flags&PackageManager.GET_PERMISSIONS) == 0) {
+                if (numMatch == permissions.length) {
+                    pi.requestedPermissions = permissions;
+                } else {
+                    pi.requestedPermissions = new String[numMatch];
+                    numMatch = 0;
+                    for (int i=0; i<permissions.length; i++) {
+                        if (tmp[i]) {
+                            pi.requestedPermissions[numMatch] = permissions[i];
+                            numMatch++;
+                        }
                     }
                 }
             }
+            list.add(pi);
         }
-        list.add(pi);
     }
 
     @Override