Merge "Add transparent frame around focused stack."
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 4f0000e..f3be0ea 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -218,6 +218,7 @@
     static final boolean DEBUG_POWER_QUICK = DEBUG_POWER || false;
     static final boolean DEBUG_MU = localLOGV || false;
     static final boolean DEBUG_IMMERSIVE = localLOGV || false;
+    static final boolean DEBUG_STACK = localLOGV || false;
     static final boolean VALIDATE_TOKENS = true;
     static final boolean SHOW_ACTIVITY_START_TIME = true;
 
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index 5aa056c..0711f74 100644
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -82,6 +82,7 @@
     static final boolean DEBUG_CONFIGURATION = ActivityManagerService.DEBUG_CONFIGURATION;
     static final boolean DEBUG_TASKS = ActivityManagerService.DEBUG_TASKS;
     static final boolean DEBUG_CLEANUP = ActivityManagerService.DEBUG_CLEANUP;
+    static final boolean DEBUG_STACK = ActivityManagerService.DEBUG_STACK;
 
     static final boolean DEBUG_STATES = ActivityStackSupervisor.DEBUG_STATES;
     static final boolean DEBUG_ADD_REMOVE = ActivityStackSupervisor.DEBUG_ADD_REMOVE;
@@ -2836,6 +2837,8 @@
         }
         final TaskRecord task = r.task;
         if (task != null && task.removeActivity(r)) {
+            if (DEBUG_STACK) Slog.i(TAG,
+                    "removeActivityFromHistoryLocked: last activity removed from " + this);
             mStackSupervisor.removeTask(task);
         }
         r.takeFromHistory();
diff --git a/services/java/com/android/server/am/ActivityStackSupervisor.java b/services/java/com/android/server/am/ActivityStackSupervisor.java
index 8b946e4..7a6687e 100644
--- a/services/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/java/com/android/server/am/ActivityStackSupervisor.java
@@ -72,6 +72,8 @@
 import java.util.List;
 
 public class ActivityStackSupervisor {
+    static final boolean DEBUG_STACK = ActivityManagerService.DEBUG_STACK;
+
     static final boolean DEBUG = ActivityManagerService.DEBUG || false;
     static final boolean DEBUG_ADD_REMOVE = DEBUG || false;
     static final boolean DEBUG_APP = DEBUG || false;
@@ -237,9 +239,11 @@
     void removeTask(TaskRecord task) {
         final ActivityStack stack = task.stack;
         if (stack.removeTask(task) && !stack.isHomeStack()) {
+            if (DEBUG_STACK) Slog.i(TAG, "removeTask: removing stack " + stack);
             mStacks.remove(stack);
             final int stackId = stack.mStackId;
             final int nextStackId = mService.mWindowManager.removeStack(stackId);
+            // TODO: Perhaps we need to let the ActivityManager determine the next focus...
             if (mFocusedStack.mStackId == stackId) {
                 mFocusedStack = nextStackId == HOME_STACK_ID ? null : getStack(nextStackId);
             }
diff --git a/services/java/com/android/server/wm/FocusedStackFrame.java b/services/java/com/android/server/wm/FocusedStackFrame.java
new file mode 100644
index 0000000..29949ad
--- /dev/null
+++ b/services/java/com/android/server/wm/FocusedStackFrame.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2013 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.server.wm;
+
+import static com.android.server.wm.WindowManagerService.DEBUG_STACK;
+
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.util.Slog;
+import android.view.Display;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+
+class FocusedStackFrame {
+    private static final String TAG = "FocusedStackFrame";
+    private static final int THICKNESS = 10;
+    private static final float ALPHA = 0.3f;
+
+    private final SurfaceControl mSurfaceControl;
+    private final Surface mSurface = new Surface();
+    private Rect mLastBounds = new Rect();
+    private Rect mBounds = new Rect();
+    private Rect mTmpDrawRect = new Rect();
+
+    public FocusedStackFrame(Display display, SurfaceSession session) {
+        SurfaceControl ctrl = null;
+        try {
+            ctrl = new SurfaceControl(session, "FocusedStackFrame",
+                1, 1, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
+            ctrl.setLayerStack(display.getLayerStack());
+            ctrl.setLayer(WindowManagerService.TYPE_LAYER_MULTIPLIER * 102);
+            ctrl.setAlpha(ALPHA);
+            mSurface.copyFrom(ctrl);
+        } catch (SurfaceControl.OutOfResourcesException e) {
+        }
+        mSurfaceControl = ctrl;
+    }
+
+    private void draw(Rect bounds, int color) {
+        if (DEBUG_STACK) Slog.i(TAG, "draw: bounds=" + bounds.toShortString() +
+                " color=" + Integer.toHexString(color));
+        mTmpDrawRect.set(bounds);
+        Canvas c = null;
+        try {
+            c = mSurface.lockCanvas(mTmpDrawRect);
+        } catch (IllegalArgumentException e) {
+        } catch (Surface.OutOfResourcesException e) {
+        }
+        if (c == null) {
+            return;
+        }
+
+        final int w = bounds.width();
+        final int h = bounds.height();
+
+        // Top
+        mTmpDrawRect.set(0, 0, w, THICKNESS);
+        c.clipRect(mTmpDrawRect, Region.Op.REPLACE);
+        c.drawColor(color);
+        // Left (not including Top or Bottom stripe).
+        mTmpDrawRect.set(0, THICKNESS, THICKNESS, h - THICKNESS);
+        c.clipRect(mTmpDrawRect, Region.Op.REPLACE);
+        c.drawColor(color);
+        // Right (not including Top or Bottom stripe).
+        mTmpDrawRect.set(w - THICKNESS, THICKNESS, w, h - THICKNESS);
+        c.clipRect(mTmpDrawRect, Region.Op.REPLACE);
+        c.drawColor(color);
+        // Bottom
+        mTmpDrawRect.set(0, h - THICKNESS, w, h);
+        c.clipRect(mTmpDrawRect, Region.Op.REPLACE);
+        c.drawColor(color);
+
+        mSurface.unlockCanvasAndPost(c);
+    }
+
+    private void positionSurface(Rect bounds) {
+        if (DEBUG_STACK) Slog.i(TAG, "positionSurface: bounds=" + bounds.toShortString());
+        mSurfaceControl.setSize(bounds.width(), bounds.height());
+        mSurfaceControl.setPosition(bounds.left, bounds.top);
+    }
+
+    // Note: caller responsible for being inside
+    // Surface.openTransaction() / closeTransaction()
+    public void setVisibility(boolean on) {
+        if (DEBUG_STACK) Slog.i(TAG, "setVisibility: on=" + on +
+                " mLastBounds=" + mLastBounds.toShortString() +
+                " mBounds=" + mBounds.toShortString());
+        if (mSurfaceControl == null) {
+            return;
+        }
+        if (on) {
+            if (!mLastBounds.equals(mBounds)) {
+                // Erase the previous rectangle.
+                positionSurface(mLastBounds);
+                draw(mLastBounds, Color.TRANSPARENT);
+                // Draw the latest rectangle.
+                positionSurface(mBounds);
+                draw(mBounds, Color.WHITE);
+                // Update the history.
+                mLastBounds.set(mBounds);
+            }
+            mSurfaceControl.show();
+        } else {
+            mSurfaceControl.hide();
+        }
+    }
+
+    public void setBounds(Rect bounds) {
+        if (DEBUG_STACK) Slog.i(TAG, "setBounds: bounds=" + bounds);
+        mBounds.set(bounds);
+    }
+}
diff --git a/services/java/com/android/server/wm/StackBox.java b/services/java/com/android/server/wm/StackBox.java
index b195f0b..83f32e9 100644
--- a/services/java/com/android/server/wm/StackBox.java
+++ b/services/java/com/android/server/wm/StackBox.java
@@ -17,8 +17,11 @@
 package com.android.server.wm;
 
 import android.graphics.Rect;
+import android.util.Slog;
 
 import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID;
+import static com.android.server.wm.WindowManagerService.DEBUG_STACK;
+import static com.android.server.wm.WindowManagerService.TAG;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -230,24 +233,28 @@
      * @return The first stackId of the resulting StackBox. */
     int remove() {
         if (mStack != null) {
+            if (DEBUG_STACK) Slog.i(TAG, "StackBox.remove: removing stackId=" + mStack.mStackId);
             mDisplayContent.mStackHistory.remove(mStack);
         }
         mDisplayContent.layoutNeeded = true;
 
         if (mParent == null) {
             // This is the top-plane stack.
+            if (DEBUG_STACK) Slog.i(TAG, "StackBox.remove: removing top plane.");
             mDisplayContent.removeStackBox(this);
             return HOME_STACK_ID;
         }
 
         StackBox sibling = isFirstChild() ? mParent.mSecond : mParent.mFirst;
         StackBox grandparent = mParent.mParent;
+        sibling.mParent = grandparent;
         if (grandparent == null) {
             // mParent is a top-plane stack. Now sibling will be.
+            if (DEBUG_STACK) Slog.i(TAG, "StackBox.remove: grandparent null");
             mDisplayContent.removeStackBox(mParent);
             mDisplayContent.addStackBox(sibling, true);
         } else {
-            sibling.mParent = grandparent;
+            if (DEBUG_STACK) Slog.i(TAG, "StackBox.remove: grandparent getting sibling");
             if (mParent.isFirstChild()) {
                 grandparent.mFirst = sibling;
             } else {
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index a5df7d9..d21b111 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -401,6 +401,7 @@
     final SurfaceSession mFxSession;
     Watermark mWatermark;
     StrictModeFlash mStrictModeFlash;
+    FocusedStackFrame mFocusedStackFrame;
 
     final float[] mTmpFloats = new float[9];
 
@@ -793,6 +794,8 @@
         SurfaceControl.openTransaction();
         try {
             createWatermarkInTransaction();
+            mFocusedStackFrame = new FocusedStackFrame(
+                    getDefaultDisplayContentLocked().getDisplay(), mFxSession);
         } finally {
             SurfaceControl.closeTransaction();
         }
@@ -3682,6 +3685,25 @@
         }
     }
 
+    void setFocusedStackFrame(TaskStack stack) {
+        if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setFocusedStackFrame");
+        SurfaceControl.openTransaction();
+        try {
+            if (stack == null) {
+                mFocusedStackFrame.setVisibility(false);
+            } else {
+                final StackBox box = stack.mStackBox;
+                final Rect bounds = box.mBounds;
+                final boolean multipleStacks = box.mParent != null;
+                mFocusedStackFrame.setBounds(bounds);
+                mFocusedStackFrame.setVisibility(multipleStacks);
+            }
+        } finally {
+            SurfaceControl.closeTransaction();
+            if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> CLOSE TRANSACTION setFocusedStackFrame");
+        }
+    }
+
     @Override
     public void setFocusedApp(IBinder token, boolean moveFocusNow) {
         if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
@@ -4746,13 +4768,16 @@
     }
 
     public int removeStack(int stackId) {
-        final TaskStack stack = mStackIdToStack.get(stackId);
-        if (stack != null) {
-            mStackIdToStack.delete(stackId);
-            int nextStackId = stack.remove();
-            stack.getDisplayContent().layoutNeeded = true;
-            performLayoutAndPlaceSurfacesLocked();
-            return nextStackId;
+        synchronized (mWindowMap) {
+            final TaskStack stack = mStackIdToStack.get(stackId);
+            if (stack != null) {
+                mStackIdToStack.delete(stackId);
+                int nextStackId = stack.remove();
+                stack.getDisplayContent().layoutNeeded = true;
+                performLayoutAndPlaceSurfacesLocked();
+                return nextStackId;
+            }
+            if (DEBUG_STACK) Slog.i(TAG, "removeStack: could not find stackId=" + stackId);
         }
         return HOME_STACK_ID;
     }
@@ -9236,6 +9261,10 @@
             }
         }
 
+        final TaskStack stack = mFocusedApp != null ?
+                mTaskIdToTask.get(mFocusedApp.groupId).mStack : null;
+        setFocusedStackFrame(stack);
+
         // Check to see if we are now in a state where the screen should
         // be enabled, because the window obscured flags have changed.
         enableScreenIfNeededLocked();