Implement display overscan support.

The window manager now keeps track of the overscan of
each display, with an API to set it.  The overscan impacts
how it positions windows in the display.  There is a new set
of APIs for windows to say they would like to go into the
overscan region.  There is a call into the window manager to
set the overscan region for a display, and it now has a
concept of display settings that it stores presistently.

Also added a new "wm" command, moving the window manager
specific commands from the "am" command to there and adding
a new now to set the overscan region.

Change-Id: Id2c8092db64fd0a982274fedac7658d82f30f9ff
diff --git a/services/java/com/android/server/display/DisplayManagerService.java b/services/java/com/android/server/display/DisplayManagerService.java
index 813c9c7..17b0662 100644
--- a/services/java/com/android/server/display/DisplayManagerService.java
+++ b/services/java/com/android/server/display/DisplayManagerService.java
@@ -324,6 +324,18 @@
     }
 
     /**
+     * Sets the overscan insets for a particular display.
+     */
+    public void setOverscan(int displayId, int left, int top, int right, int bottom) {
+        synchronized (mSyncRoot) {
+            LogicalDisplay display = mLogicalDisplays.get(displayId);
+            if (display != null) {
+                display.setOverscan(left, top, right, bottom);
+            }
+        }
+    }
+
+    /**
      * Called by the window manager to perform traversals while holding a
      * surface flinger transaction.
      */
diff --git a/services/java/com/android/server/display/LogicalDisplay.java b/services/java/com/android/server/display/LogicalDisplay.java
index 1583137..424ec36 100644
--- a/services/java/com/android/server/display/LogicalDisplay.java
+++ b/services/java/com/android/server/display/LogicalDisplay.java
@@ -143,6 +143,19 @@
         }
     }
 
+    public void setOverscan(int left, int top, int right, int bottom) {
+        mInfo.overscanLeft = left;
+        mInfo.overscanTop = top;
+        mInfo.overscanRight = right;
+        mInfo.overscanBottom = bottom;
+        if (mOverrideDisplayInfo != null) {
+            mOverrideDisplayInfo.overscanLeft = left;
+            mOverrideDisplayInfo.overscanTop = top;
+            mOverrideDisplayInfo.overscanRight = right;
+            mOverrideDisplayInfo.overscanBottom = bottom;
+        }
+    }
+
     /**
      * Returns true if the logical display is in a valid state.
      * This method should be checked after calling {@link #updateLocked} to handle the
diff --git a/services/java/com/android/server/wm/DisplayContent.java b/services/java/com/android/server/wm/DisplayContent.java
index 89e0f17..cc7c8b0 100644
--- a/services/java/com/android/server/wm/DisplayContent.java
+++ b/services/java/com/android/server/wm/DisplayContent.java
@@ -19,6 +19,9 @@
 import static com.android.server.wm.WindowManagerService.FORWARD_ITERATOR;
 import static com.android.server.wm.WindowManagerService.REVERSE_ITERATOR;
 
+import android.graphics.Rect;
+import android.os.Debug;
+import android.util.Slog;
 import android.util.SparseArray;
 import android.view.Display;
 import android.view.DisplayInfo;
@@ -316,6 +319,7 @@
             pw.print("x"); pw.print(mDisplayInfo.smallestNominalAppHeight);
             pw.print("-"); pw.print(mDisplayInfo.largestNominalAppWidth);
             pw.print("x"); pw.println(mDisplayInfo.largestNominalAppHeight);
+            pw.print(subPrefix); pw.print("layoutNeeded="); pw.println(layoutNeeded);
             AppTokenIterator iterator = getTmpAppIterator(REVERSE_ITERATOR);
             int ndx = iterator.size() - 1;
             if (ndx >= 0) {
@@ -360,7 +364,6 @@
                     pw.println();
                 }
             }
-        pw.print(subPrefix); pw.print("layoutNeeded="); pw.println(layoutNeeded);
         pw.println();
     }
 }
diff --git a/services/java/com/android/server/wm/DisplaySettings.java b/services/java/com/android/server/wm/DisplaySettings.java
new file mode 100644
index 0000000..34d1a64
--- /dev/null
+++ b/services/java/com/android/server/wm/DisplaySettings.java
@@ -0,0 +1,224 @@
+/*
+ * 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 android.content.Context;
+import android.graphics.Rect;
+import android.os.Environment;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.Xml;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.HashMap;
+
+/**
+ * Current persistent settings about a display
+ */
+public class DisplaySettings {
+    private static final String TAG = WindowManagerService.TAG;
+
+    private final Context mContext;
+    private final AtomicFile mFile;
+    private final HashMap<String, Entry> mEntries = new HashMap<String, Entry>();
+
+    public static class Entry {
+        public final String name;
+        public int overscanLeft;
+        public int overscanTop;
+        public int overscanRight;
+        public int overscanBottom;
+
+        public Entry(String _name) {
+            name = _name;
+        }
+    }
+
+    public DisplaySettings(Context context) {
+        mContext = context;
+        File dataDir = Environment.getDataDirectory();
+        File systemDir = new File(dataDir, "system");
+        mFile = new AtomicFile(new File(systemDir, "display_settings.xml"));
+    }
+
+    public void getOverscanLocked(String name, Rect outRect) {
+        Entry entry = mEntries.get(name);
+        if (entry != null) {
+            outRect.left = entry.overscanLeft;
+            outRect.top = entry.overscanTop;
+            outRect.right = entry.overscanRight;
+            outRect.bottom = entry.overscanBottom;
+        } else {
+            outRect.set(0, 0, 0, 0);
+        }
+    }
+
+    public void setOverscanLocked(String name, int left, int top, int right, int bottom) {
+        if (left == 0 && top == 0 && right == 0 && bottom == 0) {
+            // Right now all we are storing is overscan; if there is no overscan,
+            // we have no need for the entry.
+            mEntries.remove(name);
+            return;
+        }
+        Entry entry = mEntries.get(name);
+        if (entry == null) {
+            entry = new Entry(name);
+            mEntries.put(name, entry);
+        }
+        entry.overscanLeft = left;
+        entry.overscanTop = top;
+        entry.overscanRight = right;
+        entry.overscanBottom = bottom;
+    }
+
+    public void readSettingsLocked() {
+        FileInputStream stream;
+        try {
+            stream = mFile.openRead();
+        } catch (FileNotFoundException e) {
+            Slog.i(TAG, "No existing display settings " + mFile.getBaseFile()
+                    + "; starting empty");
+            return;
+        }
+        boolean success = false;
+        try {
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(stream, null);
+            int type;
+            while ((type = parser.next()) != XmlPullParser.START_TAG
+                    && type != XmlPullParser.END_DOCUMENT) {
+                ;
+            }
+
+            if (type != XmlPullParser.START_TAG) {
+                throw new IllegalStateException("no start tag found");
+            }
+
+            int outerDepth = parser.getDepth();
+            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                    && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+                if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                    continue;
+                }
+
+                String tagName = parser.getName();
+                if (tagName.equals("display")) {
+                    readDisplay(parser);
+                } else {
+                    Slog.w(TAG, "Unknown element under <display-settings>: "
+                            + parser.getName());
+                    XmlUtils.skipCurrentTag(parser);
+                }
+            }
+            success = true;
+        } catch (IllegalStateException e) {
+            Slog.w(TAG, "Failed parsing " + e);
+        } catch (NullPointerException e) {
+            Slog.w(TAG, "Failed parsing " + e);
+        } catch (NumberFormatException e) {
+            Slog.w(TAG, "Failed parsing " + e);
+        } catch (XmlPullParserException e) {
+            Slog.w(TAG, "Failed parsing " + e);
+        } catch (IOException e) {
+            Slog.w(TAG, "Failed parsing " + e);
+        } catch (IndexOutOfBoundsException e) {
+            Slog.w(TAG, "Failed parsing " + e);
+        } finally {
+            if (!success) {
+                mEntries.clear();
+            }
+            try {
+                stream.close();
+            } catch (IOException e) {
+            }
+        }
+    }
+
+    private int getIntAttribute(XmlPullParser parser, String name) {
+        try {
+            String str = parser.getAttributeValue(null, name);
+            return str != null ? Integer.parseInt(str) : 0;
+        } catch (NumberFormatException e) {
+            return 0;
+        }
+    }
+
+    private void readDisplay(XmlPullParser parser) throws NumberFormatException,
+            XmlPullParserException, IOException {
+        String name = parser.getAttributeValue(null, "name");
+        if (name != null) {
+            Entry entry = new Entry(name);
+            entry.overscanLeft = getIntAttribute(parser, "overscanLeft");
+            entry.overscanTop = getIntAttribute(parser, "overscanTop");
+            entry.overscanRight = getIntAttribute(parser, "overscanRight");
+            entry.overscanBottom = getIntAttribute(parser, "overscanBottom");
+            mEntries.put(name, entry);
+        }
+        XmlUtils.skipCurrentTag(parser);
+    }
+
+    public void writeSettingsLocked() {
+        FileOutputStream stream;
+        try {
+            stream = mFile.startWrite();
+        } catch (IOException e) {
+            Slog.w(TAG, "Failed to write display settings: " + e);
+            return;
+        }
+
+        try {
+            XmlSerializer out = new FastXmlSerializer();
+            out.setOutput(stream, "utf-8");
+            out.startDocument(null, true);
+            out.startTag(null, "display-settings");
+
+            for (Entry entry : mEntries.values()) {
+                out.startTag(null, "display");
+                out.attribute(null, "name", entry.name);
+                if (entry.overscanLeft != 0) {
+                    out.attribute(null, "overscanLeft", Integer.toString(entry.overscanLeft));
+                }
+                if (entry.overscanTop != 0) {
+                    out.attribute(null, "overscanTop", Integer.toString(entry.overscanTop));
+                }
+                if (entry.overscanRight != 0) {
+                    out.attribute(null, "overscanRight", Integer.toString(entry.overscanRight));
+                }
+                if (entry.overscanBottom != 0) {
+                    out.attribute(null, "overscanBottom", Integer.toString(entry.overscanBottom));
+                }
+                out.endTag(null, "display");
+            }
+
+            out.endTag(null, "display-settings");
+            out.endDocument();
+            mFile.finishWrite(stream);
+        } catch (IOException e) {
+            Slog.w(TAG, "Failed to write display settings, restoring backup.", e);
+            mFile.failWrite(stream);
+        }
+    }
+}
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index d38273d..b7637b9 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -320,6 +320,8 @@
 
     final AppOpsManager mAppOps;
 
+    final DisplaySettings mDisplaySettings;
+
     /**
      * All currently active sessions with clients.
      */
@@ -731,6 +733,8 @@
                 com.android.internal.R.bool.config_sf_limitedAlpha);
         mDisplayManagerService = displayManager;
         mHeadless = displayManager.isHeadless();
+        mDisplaySettings = new DisplaySettings(context);
+        mDisplaySettings.readSettingsLocked();
 
         mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
         mDisplayManager.registerDisplayListener(this, null);
@@ -7089,6 +7093,15 @@
 
     @Override
     public void setForcedDisplaySize(int displayId, int width, int height) {
+        if (mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.WRITE_SECURE_SETTINGS) !=
+                PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Must hold permission " +
+                    android.Manifest.permission.WRITE_SECURE_SETTINGS);
+        }
+        if (displayId != Display.DEFAULT_DISPLAY) {
+            throw new IllegalArgumentException("Can only set the default display");
+        }
         synchronized(mWindowMap) {
             // Set some sort of reasonable bounds on the size of the display that we
             // will try to emulate.
@@ -7160,6 +7173,15 @@
 
     @Override
     public void clearForcedDisplaySize(int displayId) {
+        if (mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.WRITE_SECURE_SETTINGS) !=
+                PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Must hold permission " +
+                    android.Manifest.permission.WRITE_SECURE_SETTINGS);
+        }
+        if (displayId != Display.DEFAULT_DISPLAY) {
+            throw new IllegalArgumentException("Can only set the default display");
+        }
         synchronized(mWindowMap) {
             final DisplayContent displayContent = getDisplayContentLocked(displayId);
             if (displayContent != null) {
@@ -7173,6 +7195,15 @@
 
     @Override
     public void setForcedDisplayDensity(int displayId, int density) {
+        if (mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.WRITE_SECURE_SETTINGS) !=
+                PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Must hold permission " +
+                    android.Manifest.permission.WRITE_SECURE_SETTINGS);
+        }
+        if (displayId != Display.DEFAULT_DISPLAY) {
+            throw new IllegalArgumentException("Can only set the default display");
+        }
         synchronized(mWindowMap) {
             final DisplayContent displayContent = getDisplayContentLocked(displayId);
             if (displayContent != null) {
@@ -7195,6 +7226,15 @@
 
     @Override
     public void clearForcedDisplayDensity(int displayId) {
+        if (mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.WRITE_SECURE_SETTINGS) !=
+                PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Must hold permission " +
+                    android.Manifest.permission.WRITE_SECURE_SETTINGS);
+        }
+        if (displayId != Display.DEFAULT_DISPLAY) {
+            throw new IllegalArgumentException("Can only set the default display");
+        }
         synchronized(mWindowMap) {
             final DisplayContent displayContent = getDisplayContentLocked(displayId);
             if (displayContent != null) {
@@ -7233,6 +7273,33 @@
         performLayoutAndPlaceSurfacesLocked();
     }
 
+    public void setOverscan(int displayId, int left, int top, int right, int bottom) {
+        if (mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.WRITE_SECURE_SETTINGS) !=
+                PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Must hold permission " +
+                    android.Manifest.permission.WRITE_SECURE_SETTINGS);
+        }
+        synchronized(mWindowMap) {
+            DisplayContent displayContent = getDisplayContentLocked(displayId);
+            if (displayContent != null) {
+                mDisplayManagerService.setOverscan(displayId, left, top, right, bottom);
+                final DisplayInfo displayInfo = displayContent.getDisplayInfo();
+                synchronized(displayContent.mDisplaySizeLock) {
+                    displayInfo.overscanLeft = left;
+                    displayInfo.overscanTop = top;
+                    displayInfo.overscanRight = right;
+                    displayInfo.overscanBottom = bottom;
+                }
+                mPolicy.setDisplayOverscan(displayContent.getDisplay(), left, top, right, bottom);
+                displayContent.layoutNeeded = true;
+                mDisplaySettings.setOverscanLocked(displayInfo.name, left, top, right, bottom);
+                mDisplaySettings.writeSettingsLocked();
+                performLayoutAndPlaceSurfacesLocked();
+            }
+        }
+    }
+
     @Override
     public boolean hasSystemNavBar() {
         return mPolicy.hasSystemNavBar();
@@ -9694,7 +9761,7 @@
         if (dumpAll) {
             pw.print("  mSystemDecorRect="); pw.print(mSystemDecorRect.toShortString());
                     pw.print(" mSystemDecorLayer="); pw.print(mSystemDecorLayer);
-                    pw.print(" mScreenRecr="); pw.println(mScreenRect.toShortString());
+                    pw.print(" mScreenRect="); pw.println(mScreenRect.toShortString());
             if (mLastStatusBarVisibility != 0) {
                 pw.print("  mLastStatusBarVisibility=0x");
                         pw.println(Integer.toHexString(mLastStatusBarVisibility));
@@ -9973,12 +10040,28 @@
         }
     }
 
+    private DisplayContent newDisplayContentLocked(final Display display) {
+        DisplayContent displayContent = new DisplayContent(display);
+        mDisplayContents.put(display.getDisplayId(), displayContent);
+        final Rect rect = new Rect();
+        DisplayInfo info = displayContent.getDisplayInfo();
+        mDisplaySettings.getOverscanLocked(info.name, rect);
+        info.overscanLeft = rect.left;
+        info.overscanTop = rect.top;
+        info.overscanRight = rect.right;
+        info.overscanBottom = rect.bottom;
+        mDisplayManagerService.setOverscan(display.getDisplayId(), rect.left, rect.top,
+                rect.right, rect.bottom);
+        mPolicy.setDisplayOverscan(displayContent.getDisplay(), rect.left, rect.top,
+                rect.right, rect.bottom);
+        return displayContent;
+    }
+
     public void createDisplayContentLocked(final Display display) {
         if (display == null) {
             throw new IllegalArgumentException("getDisplayContent: display must not be null");
         }
-        final DisplayContent displayContent = new DisplayContent(display);
-        mDisplayContents.put(display.getDisplayId(), displayContent);
+        newDisplayContentLocked(display);
     }
 
     /**
@@ -9992,8 +10075,7 @@
         if (displayContent == null) {
             final Display display = mDisplayManager.getDisplay(displayId);
             if (display != null) {
-                displayContent = new DisplayContent(display);
-                mDisplayContents.put(displayId, displayContent);
+                displayContent = newDisplayContentLocked(display);
             }
         }
         return displayContent;