Refactor DisplayManagerService to be functional.

Change-Id: Ieac1eca172be5dc5db45302d3afa26188acd4d6d
diff --git a/services/java/com/android/server/display/DisplayAdapter.java b/services/java/com/android/server/display/DisplayAdapter.java
index e150bf8..b623906 100644
--- a/services/java/com/android/server/display/DisplayAdapter.java
+++ b/services/java/com/android/server/display/DisplayAdapter.java
@@ -16,14 +16,32 @@
 
 package com.android.server.display;
 
+import android.view.Display;
+
 /**
- * A display adapter makes one or more display devices available to the system.
+ * A display adapter makes a single display devices available to the system.
  * <p>
  * For now, all display adapters are registered in the system server but
  * in principle it could be done from other processes.
  * </p>
  */
 public abstract class DisplayAdapter {
+    /** The current logical Display assignment for this adapter. Will change if other logical
+     * display is assigned to this adapter */
+    private int mDisplayId = Display.NO_DISPLAY;
+
+    /** Assign the displayId
+     * @hide */
+    public void setDisplayId(int displayId) {
+        mDisplayId = displayId;
+    }
+
+    /** Retrieve the displayId
+     * @hide */
+    public int getDisplayId() {
+        return mDisplayId;
+    }
+
     /**
      * Gets the display adapter name.
      * @return The display adapter name.
@@ -31,5 +49,5 @@
     public abstract String getName();
 
     // TODO: dynamically register display devices
-    public abstract DisplayDevice[] getDisplayDevices();
+    public abstract DisplayDevice getDisplayDevice();
 }
diff --git a/services/java/com/android/server/display/DisplayManagerService.java b/services/java/com/android/server/display/DisplayManagerService.java
index e714064..b9c9ffd 100644
--- a/services/java/com/android/server/display/DisplayManagerService.java
+++ b/services/java/com/android/server/display/DisplayManagerService.java
@@ -22,6 +22,8 @@
 import android.hardware.display.IDisplayManager;
 import android.os.Binder;
 import android.os.SystemProperties;
+import android.util.Slog;
+import android.util.SparseArray;
 import android.view.Display;
 import android.view.DisplayInfo;
 import android.view.Surface;
@@ -47,17 +49,30 @@
 
     private Context mContext;
     private final boolean mHeadless;
+
+    private int mDisplayIdSeq = Display.DEFAULT_DISPLAY;
+
+    /** All registered DisplayAdapters. */
     private final ArrayList<DisplayAdapter> mDisplayAdapters = new ArrayList<DisplayAdapter>();
 
-    // TODO: represent this as a map between logical and physical devices
-    private DisplayInfo mDefaultDisplayInfo;
-    private DisplayDevice mDefaultDisplayDevice;
-    private DisplayDeviceInfo mDefaultDisplayDeviceInfo;
+    /** All the DisplayAdapters showing the given displayId. */
+    private final SparseArray<ArrayList<DisplayAdapter>> mLogicalToPhysicals =
+            new SparseArray<ArrayList<DisplayAdapter>>();
+
+    /** All the DisplayInfos in the system indexed by deviceId */
+    private final SparseArray<DisplayInfo> mDisplayInfos = new SparseArray<DisplayInfo>();
 
     public DisplayManagerService() {
         mHeadless = SystemProperties.get(SYSTEM_HEADLESS).equals("1");
-        registerDisplayAdapters();
-        initializeDefaultDisplay();
+        registerDefaultDisplayAdapter();
+    }
+
+    private void registerDefaultDisplayAdapter() {
+        if (mHeadless) {
+            registerDisplayAdapter(new HeadlessDisplayAdapter());
+        } else {
+            registerDisplayAdapter(new SurfaceFlingerDisplayAdapter());
+        }
     }
 
     public void setContext(Context context) {
@@ -65,13 +80,6 @@
     }
 
     // FIXME: this isn't the right API for the long term
-    public void setDefaultDisplayInfo(DisplayInfo info) {
-        synchronized (mLock) {
-            mDefaultDisplayInfo.copyFrom(info);
-        }
-    }
-
-    // FIXME: this isn't the right API for the long term
     public void getDefaultExternalDisplayDeviceInfo(DisplayDeviceInfo info) {
         // hardcoded assuming 720p touch screen plugged into HDMI and USB
         // need to redesign this
@@ -83,17 +91,187 @@
         return mHeadless;
     }
 
+    /**
+     * Save away new DisplayInfo data.
+     * @param displayId The local DisplayInfo to store the new data in.
+     * @param info The new data to be stored.
+     */
+    public void setDisplayInfo(int displayId, DisplayInfo info) {
+        synchronized (mLock) {
+            DisplayInfo localInfo = mDisplayInfos.get(displayId);
+            if (localInfo == null) {
+                localInfo = new DisplayInfo();
+                mDisplayInfos.put(displayId, localInfo);
+            }
+            localInfo.copyFrom(info);
+        }
+    }
+
+    /**
+     * Return requested DisplayInfo.
+     * @param displayId The data to retrieve.
+     * @param outInfo The structure to receive the data.
+     */
     @Override // Binder call
     public boolean getDisplayInfo(int displayId, DisplayInfo outInfo) {
         synchronized (mLock) {
-            if (displayId == Display.DEFAULT_DISPLAY) {
-                outInfo.copyFrom(mDefaultDisplayInfo);
-                return true;
+            DisplayInfo localInfo = mDisplayInfos.get(displayId);
+            if (localInfo == null) {
+                return false;
             }
-            return false;
+            outInfo.copyFrom(localInfo);
+            return true;
         }
     }
 
+    /**
+     * Inform the service of a new physical display. A new logical displayId is created and the new
+     * physical display is immediately bound to it. Use removeAdapterFromDisplay to disconnect it.
+     *
+     * @param adapter The wrapper for information associated with the physical display.
+     */
+    public void registerDisplayAdapter(DisplayAdapter adapter) {
+        synchronized (mLock) {
+            int displayId = mDisplayIdSeq++;
+            adapter.setDisplayId(displayId);
+
+            createDisplayInfoLocked(displayId, adapter);
+
+            ArrayList<DisplayAdapter> list = new ArrayList<DisplayAdapter>();
+            list.add(adapter);
+            mLogicalToPhysicals.put(displayId, list);
+
+            mDisplayAdapters.add(adapter);
+        }
+
+        // TODO: Notify SurfaceFlinger of new addition.
+    }
+
+    /**
+     * Connect a logical display to a physical display. Will remove the physical display from any
+     * logical display it is currently attached to.
+     *
+     * @param displayId The logical display. Will be created if it does not already exist.
+     * @param adapter The physical display.
+     */
+    public void addAdapterToDisplay(int displayId, DisplayAdapter adapter) {
+        if (adapter == null) {
+            // TODO: Or throw NPE?
+            Slog.e(TAG, "addDeviceToDisplay: Attempt to add null adapter");
+            return;
+        }
+
+        synchronized (mLock) {
+            if (!mDisplayAdapters.contains(adapter)) {
+                // TOOD: Handle unregistered adapter with exception or return value.
+                Slog.e(TAG, "addDeviceToDisplay: Attempt to add an unregistered adapter");
+                return;
+            }
+
+            DisplayInfo displayInfo = mDisplayInfos.get(displayId);
+            if (displayInfo == null) {
+                createDisplayInfoLocked(displayId, adapter);
+            }
+
+            Integer oldDisplayId = adapter.getDisplayId();
+            if (oldDisplayId != Display.NO_DISPLAY) {
+                if (oldDisplayId == displayId) {
+                    // adapter already added to displayId.
+                    return;
+                }
+
+                removeAdapterLocked(adapter);
+            }
+
+            ArrayList<DisplayAdapter> list = mLogicalToPhysicals.get(displayId);
+            if (list == null) {
+                list = new ArrayList<DisplayAdapter>();
+                mLogicalToPhysicals.put(displayId, list);
+            }
+
+            list.add(adapter);
+            adapter.setDisplayId(displayId);
+        }
+
+        // TODO: Notify SurfaceFlinger of new addition.
+    }
+
+    /**
+     * Disconnect the physical display from whichever logical display it is attached to.
+     * @param adapter The physical display to detach.
+     */
+    public void removeAdapterFromDisplay(DisplayAdapter adapter) {
+        if (adapter == null) {
+            // TODO: Or throw NPE?
+            return;
+        }
+
+        synchronized (mLock) {
+            if (!mDisplayAdapters.contains(adapter)) {
+                // TOOD: Handle unregistered adapter with exception or return value.
+                Slog.e(TAG, "removeDeviceFromDisplay: Attempt to remove an unregistered adapter");
+                return;
+            }
+
+            removeAdapterLocked(adapter);
+        }
+
+        // TODO: Notify SurfaceFlinger of removal.
+    }
+
+    /**
+     * Create a new logical DisplayInfo and fill it in with information from the physical display.
+     * @param displayId The logical identifier.
+     * @param adapter The physical display for initial values.
+     */
+    private void createDisplayInfoLocked(int displayId, DisplayAdapter adapter) {
+        DisplayInfo displayInfo = new DisplayInfo();
+        DisplayDeviceInfo deviceInfo = new DisplayDeviceInfo();
+        adapter.getDisplayDevice().getInfo(deviceInfo);
+        copyDisplayInfoFromDeviceInfo(displayInfo, deviceInfo);
+        mDisplayInfos.put(displayId, displayInfo);
+    }
+
+    /**
+     * Disconnect a physical display from its logical display. If there are no more physical
+     * displays attached to the logical display, delete the logical display.
+     * @param adapter The physical display to detach.
+     */
+    void removeAdapterLocked(DisplayAdapter adapter) {
+        int displayId = adapter.getDisplayId();
+        adapter.setDisplayId(Display.NO_DISPLAY);
+
+        ArrayList<DisplayAdapter> list = mLogicalToPhysicals.get(displayId);
+        if (list != null) {
+            list.remove(adapter);
+            if (list.isEmpty()) {
+                mLogicalToPhysicals.remove(displayId);
+                // TODO: Keep count of Windows attached to logical display and don't delete if
+                // there are any outstanding. Also, what keeps the WindowManager from continuing
+                // to use the logical display?
+                mDisplayInfos.remove(displayId);
+            }
+        }
+    }
+
+    private void copyDisplayInfoFromDeviceInfo(DisplayInfo displayInfo,
+                                               DisplayDeviceInfo deviceInfo) {
+        // Bootstrap the logical display using the physical display.
+        displayInfo.appWidth = deviceInfo.width;
+        displayInfo.appHeight = deviceInfo.height;
+        displayInfo.logicalWidth = deviceInfo.width;
+        displayInfo.logicalHeight = deviceInfo.height;
+        displayInfo.rotation = Surface.ROTATION_0;
+        displayInfo.refreshRate = deviceInfo.refreshRate;
+        displayInfo.logicalDensityDpi = deviceInfo.densityDpi;
+        displayInfo.physicalXDpi = deviceInfo.xDpi;
+        displayInfo.physicalYDpi = deviceInfo.yDpi;
+        displayInfo.smallestNominalAppWidth = deviceInfo.width;
+        displayInfo.smallestNominalAppHeight = deviceInfo.height;
+        displayInfo.largestNominalAppWidth = deviceInfo.width;
+        displayInfo.largestNominalAppHeight = deviceInfo.height;
+    }
+
     @Override // Binder call
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (mContext == null
@@ -110,46 +288,11 @@
 
         DisplayDeviceInfo info = new DisplayDeviceInfo();
         for (DisplayAdapter adapter : mDisplayAdapters) {
-            pw.println("Displays for adapter " + adapter.getName());
-            for (DisplayDevice device : adapter.getDisplayDevices()) {
-                device.getInfo(info);
-                pw.print("  ");
-                pw.println(info);
-            }
+            pw.println("Display for adapter " + adapter.getName());
+            DisplayDevice device = adapter.getDisplayDevice();
+            pw.print("  ");
+            device.getInfo(info);
+            pw.println(info);
         }
     }
-
-    private void registerDisplayAdapters() {
-        if (mHeadless) {
-            registerDisplayAdapter(new HeadlessDisplayAdapter());
-        } else {
-            registerDisplayAdapter(new SurfaceFlingerDisplayAdapter());
-        }
-    }
-
-    private void registerDisplayAdapter(DisplayAdapter adapter) {
-        // TODO: do this dynamically
-        mDisplayAdapters.add(adapter);
-        mDefaultDisplayDevice = adapter.getDisplayDevices()[0];
-        mDefaultDisplayDeviceInfo = new DisplayDeviceInfo();
-        mDefaultDisplayDevice.getInfo(mDefaultDisplayDeviceInfo);
-    }
-
-    private void initializeDefaultDisplay() {
-        // Bootstrap the default logical display using the default physical display.
-        mDefaultDisplayInfo = new DisplayInfo();
-        mDefaultDisplayInfo.appWidth = mDefaultDisplayDeviceInfo.width;
-        mDefaultDisplayInfo.appHeight = mDefaultDisplayDeviceInfo.height;
-        mDefaultDisplayInfo.logicalWidth = mDefaultDisplayDeviceInfo.width;
-        mDefaultDisplayInfo.logicalHeight = mDefaultDisplayDeviceInfo.height;
-        mDefaultDisplayInfo.rotation = Surface.ROTATION_0;
-        mDefaultDisplayInfo.refreshRate = mDefaultDisplayDeviceInfo.refreshRate;
-        mDefaultDisplayInfo.logicalDensityDpi = mDefaultDisplayDeviceInfo.densityDpi;
-        mDefaultDisplayInfo.physicalXDpi = mDefaultDisplayDeviceInfo.xDpi;
-        mDefaultDisplayInfo.physicalYDpi = mDefaultDisplayDeviceInfo.yDpi;
-        mDefaultDisplayInfo.smallestNominalAppWidth = mDefaultDisplayDeviceInfo.width;
-        mDefaultDisplayInfo.smallestNominalAppHeight = mDefaultDisplayDeviceInfo.height;
-        mDefaultDisplayInfo.largestNominalAppWidth = mDefaultDisplayDeviceInfo.width;
-        mDefaultDisplayInfo.largestNominalAppHeight = mDefaultDisplayDeviceInfo.height;
-    }
 }
diff --git a/services/java/com/android/server/display/HeadlessDisplayAdapter.java b/services/java/com/android/server/display/HeadlessDisplayAdapter.java
index cd18c37..3eaf40f 100644
--- a/services/java/com/android/server/display/HeadlessDisplayAdapter.java
+++ b/services/java/com/android/server/display/HeadlessDisplayAdapter.java
@@ -40,7 +40,7 @@
     }
 
     @Override
-    public DisplayDevice[] getDisplayDevices() {
-        return new DisplayDevice[] { mDefaultDisplay };
+    public DisplayDevice getDisplayDevice() {
+        return mDefaultDisplay;
     }
 }
diff --git a/services/java/com/android/server/display/SurfaceFlingerDisplayAdapter.java b/services/java/com/android/server/display/SurfaceFlingerDisplayAdapter.java
index 89934d3..539f7c1 100644
--- a/services/java/com/android/server/display/SurfaceFlingerDisplayAdapter.java
+++ b/services/java/com/android/server/display/SurfaceFlingerDisplayAdapter.java
@@ -35,7 +35,7 @@
     }
 
     @Override
-    public DisplayDevice[] getDisplayDevices() {
-        return new DisplayDevice[] { mDefaultDisplay };
+    public DisplayDevice getDisplayDevice() {
+        return mDefaultDisplay;
     }
 }
diff --git a/services/java/com/android/server/wm/DisplayContent.java b/services/java/com/android/server/wm/DisplayContent.java
index d4c17c5..2305c88 100644
--- a/services/java/com/android/server/wm/DisplayContent.java
+++ b/services/java/com/android/server/wm/DisplayContent.java
@@ -18,6 +18,8 @@
 
 import android.view.DisplayInfo;
 
+import com.android.server.display.DisplayManagerService;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 
@@ -56,10 +58,13 @@
     int mInitialDisplayHeight = 0;
     int mBaseDisplayWidth = 0;
     int mBaseDisplayHeight = 0;
+    final DisplayManagerService mDisplayManager;
     final DisplayInfo mDisplayInfo = new DisplayInfo();
 
-    DisplayContent(final int displayId) {
+    DisplayContent(DisplayManagerService displayManager, final int displayId) {
+        mDisplayManager = displayManager;
         mDisplayId = displayId;
+        displayManager.getDisplayInfo(displayId, mDisplayInfo);
     }
 
     int getDisplayId() {
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index 8b5c923..e763a56 100755
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -4876,7 +4876,7 @@
             final int pos = findWindowOffsetLocked(windows, tokenPos);
             reAddAppWindowsLocked(displayContent, pos, wtoken);
 
-            if (updateFocusAndLayout && !updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES, 
+            if (updateFocusAndLayout && !updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES,
                     false /*updateInputWindows*/)) {
                 assignLayersLocked(windows);
             }
@@ -4927,7 +4927,7 @@
 
         mInputMonitor.setUpdateInputWindowsNeededLw();
 
-        // Note that the above updateFocusedWindowLocked used to sit here. 
+        // Note that the above updateFocusedWindowLocked used to sit here.
 
         mLayoutNeeded = true;
         performLayoutAndPlaceSurfacesLocked();
@@ -6572,7 +6572,7 @@
             displayInfo.appHeight = appHeight;
             displayInfo.getLogicalMetrics(mRealDisplayMetrics, null);
             displayInfo.getAppMetrics(mDisplayMetrics, null);
-            mDisplayManager.setDefaultDisplayInfo(displayInfo);
+            mDisplayManager.setDisplayInfo(displayContent.getDisplayId(), displayInfo);
 
             mAnimator.setDisplayDimensions(dw, dh, appWidth, appHeight);
         }
@@ -6882,48 +6882,51 @@
 
     public void displayReady() {
         displayReady(Display.DEFAULT_DISPLAY);
+
+        synchronized(mWindowMap) {
+            WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
+            mDisplay = wm.getDefaultDisplay();
+            mIsTouchDevice = mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_TOUCHSCREEN);
+
+            final DisplayInfo displayInfo = getDefaultDisplayInfo();
+            mAnimator.setDisplayDimensions(displayInfo.logicalWidth, displayInfo.logicalHeight,
+                displayInfo.appWidth, displayInfo.appHeight);
+
+            DisplayDeviceInfo info = new DisplayDeviceInfo();
+            mDisplayManager.getDefaultExternalDisplayDeviceInfo(info);
+
+            final DisplayContent displayContent = getDefaultDisplayContent();
+            mInputManager.setDisplaySize(Display.DEFAULT_DISPLAY,
+                    displayContent.mInitialDisplayWidth, displayContent.mInitialDisplayHeight,
+                    info.width, info.height);
+            mInputManager.setDisplayOrientation(Display.DEFAULT_DISPLAY,
+                    mDisplay.getRotation(), Surface.ROTATION_0);
+            mPolicy.setInitialDisplaySize(mDisplay,
+                    displayContent.mInitialDisplayWidth, displayContent.mInitialDisplayHeight);
+        }
     }
 
     public void displayReady(int displayId) {
         synchronized(mWindowMap) {
-            if (mDisplay != null) {
-                throw new IllegalStateException("Display already initialized");
-            }
-            WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
-            mDisplay = wm.getDefaultDisplay();
-            mIsTouchDevice = mContext.getPackageManager().hasSystemFeature(
-                    PackageManager.FEATURE_TOUCHSCREEN);
-
             final DisplayContent displayContent = getDisplayContent(displayId);
+            final DisplayInfo displayInfo;
             synchronized(displayContent.mDisplaySizeLock) {
                 // Bootstrap the default logical display from the display manager.
-                final DisplayInfo displayInfo = displayContent.getDisplayInfo();
+                displayInfo = displayContent.getDisplayInfo();
                 mDisplayManager.getDisplayInfo(displayId, displayInfo);
                 displayContent.mInitialDisplayWidth = displayInfo.logicalWidth;
                 displayContent.mInitialDisplayHeight = displayInfo.logicalHeight;
                 displayContent.mBaseDisplayWidth = displayContent.mInitialDisplayWidth;
                 displayContent.mBaseDisplayHeight = displayContent.mInitialDisplayHeight;
-
-                mAnimator.setDisplayDimensions(displayInfo.logicalWidth, displayInfo.logicalHeight,
-                        displayInfo.appWidth, displayInfo.appHeight);
             }
-
-            DisplayDeviceInfo info = new DisplayDeviceInfo();
-            mDisplayManager.getDefaultExternalDisplayDeviceInfo(info);
-            mInputManager.setDisplaySize(displayId,
-                    displayContent.mInitialDisplayWidth, displayContent.mInitialDisplayHeight,
-                    info.width, info.height);
-            mInputManager.setDisplayOrientation(displayId,
-                    mDisplay.getRotation(), Surface.ROTATION_0);
-            mPolicy.setInitialDisplaySize(mDisplay,
-                    displayContent.mInitialDisplayWidth, displayContent.mInitialDisplayHeight);
         }
 
         try {
             mActivityManager.updateConfiguration(null);
         } catch (RemoteException e) {
         }
-        
+
         synchronized (mWindowMap) {
             readForcedDisplaySizeLocked(getDisplayContent(displayId));
         }
@@ -10325,7 +10328,7 @@
     public DisplayContent getDisplayContent(final int displayId) {
         DisplayContent displayContent = mDisplayContents.get(displayId);
         if (displayContent == null) {
-            displayContent = new DisplayContent(displayId);
+            displayContent = new DisplayContent(mDisplayManager, displayId);
             mDisplayContents.put(displayId, displayContent);
         }
         return displayContent;