From bd6e1500aedc5461e832f69e76341bff0e55fa2b Mon Sep 17 00:00:00 2001 From: Jeff Brown Date: Tue, 28 Aug 2012 03:27:37 -0700 Subject: Add initial multi-display support. Split the DisplayManager into two parts. One part is bound to a Context and takes care of Display compatibility and caching Display objects on behalf of the Context. The other part is global and takes care of communicating with the DisplayManagerService, handling callbacks, and caching DisplayInfo objects on behalf of the process. Implemented support for enumerating Displays and getting callbacks when displays are added, removed or changed. Elaborated the roles of DisplayManagerService, DisplayAdapter, and DisplayDevice. We now support having multiple display adapters registered, each of which can register multiple display devices and configure them dynamically. Added an OverlayDisplayAdapter which is used to simulate secondary displays by means of overlay windows. Different configurations of overlays can be selected using a new setting in the Developer Settings panel. The overlays can be repositioned and resized by the user for convenience. At the moment, all displays are mirrors of display 0 and no display transformations are applied. This will be improved in future patches. Refactored the way that the window manager creates its threads. The OverlayDisplayAdapter needs to be able to use hardware acceleration so it must share the same UI thread as the Keyguard and window manager policy. We now handle this explicitly as part of starting up the system server. This puts us in a better position to consider how we might want to share (or not share) Loopers among components. Overlay displays are disabled when in safe mode or in only-core mode to reduce the number of dependencies started in these modes. Change-Id: Ic2a661d5448dde01b095ab150697cb6791d69bb5 --- services/java/com/android/server/SystemServer.java | 62 ++- .../com/android/server/display/DisplayAdapter.java | 79 ++- .../com/android/server/display/DisplayDevice.java | 30 +- .../android/server/display/DisplayDeviceInfo.java | 20 +- .../server/display/DisplayManagerService.java | 534 ++++++++++++++++++--- .../server/display/HeadlessDisplayAdapter.java | 36 +- .../server/display/LocalDisplayAdapter.java | 52 +- .../server/display/OverlayDisplayAdapter.java | 530 ++++++++++++++++++++ .../server/power/DisplayPowerController.java | 40 +- .../android/server/wm/WindowManagerService.java | 150 ++---- 10 files changed, 1253 insertions(+), 280 deletions(-) create mode 100644 services/java/com/android/server/display/OverlayDisplayAdapter.java (limited to 'services/java/com') diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 7097891fd1f0..48b12155e0cf 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -28,6 +28,8 @@ import android.content.pm.IPackageManager; import android.content.res.Configuration; import android.media.AudioService; import android.net.wifi.p2p.WifiP2pService; +import android.os.Handler; +import android.os.HandlerThread; import android.os.Looper; import android.os.RemoteException; import android.os.SchedulingPolicyService; @@ -147,7 +149,52 @@ class ServerThread extends Thread { CommonTimeManagementService commonTimeMgmtService = null; InputManagerService inputManager = null; + // Create a shared handler thread for UI within the system server. + // This thread is used by at least the following components: + // - WindowManagerPolicy + // - KeyguardViewManager + // - DisplayManagerService + HandlerThread uiHandlerThread = new HandlerThread("UI"); + uiHandlerThread.start(); + Handler uiHandler = new Handler(uiHandlerThread.getLooper()); + uiHandler.post(new Runnable() { + @Override + public void run() { + //Looper.myLooper().setMessageLogging(new LogPrinter( + // Log.VERBOSE, "WindowManagerPolicy", Log.LOG_ID_SYSTEM)); + android.os.Process.setThreadPriority( + android.os.Process.THREAD_PRIORITY_FOREGROUND); + android.os.Process.setCanSelfBackground(false); + + // For debug builds, log event loop stalls to dropbox for analysis. + if (StrictMode.conditionallyEnableDebugLogging()) { + Slog.i(TAG, "Enabled StrictMode logging for UI Looper"); + } + } + }); + + // Create a handler thread just for the window manager to enjoy. + HandlerThread wmHandlerThread = new HandlerThread("WindowManager"); + wmHandlerThread.start(); + Handler wmHandler = new Handler(wmHandlerThread.getLooper()); + wmHandler.post(new Runnable() { + @Override + public void run() { + //Looper.myLooper().setMessageLogging(new LogPrinter( + // android.util.Log.DEBUG, TAG, android.util.Log.LOG_ID_SYSTEM)); + android.os.Process.setThreadPriority( + android.os.Process.THREAD_PRIORITY_DISPLAY); + android.os.Process.setCanSelfBackground(false); + + // For debug builds, log event loop stalls to dropbox for analysis. + if (StrictMode.conditionallyEnableDebugLogging()) { + Slog.i(TAG, "Enabled StrictMode logging for UI Looper"); + } + } + }); + // Critical services... + boolean onlyCore = false; try { Slog.i(TAG, "Entropy Mixer"); ServiceManager.addService("entropy", new EntropyMixer()); @@ -160,7 +207,7 @@ class ServerThread extends Thread { context = ActivityManagerService.main(factoryTest); Slog.i(TAG, "Display Manager"); - display = new DisplayManagerService(context); + display = new DisplayManagerService(context, uiHandler); ServiceManager.addService(Context.DISPLAY_SERVICE, display, true); Slog.i(TAG, "Telephony Registry"); @@ -172,10 +219,14 @@ class ServerThread extends Thread { AttributeCache.init(context); + if (!display.waitForDefaultDisplay()) { + reportWtf("Timeout waiting for default display to be initialized.", + new Throwable()); + } + Slog.i(TAG, "Package Manager"); // Only run "core" apps if we're encrypting the device. String cryptState = SystemProperties.get("vold.decrypt"); - boolean onlyCore = false; if (ENCRYPTING_STATE.equals(cryptState)) { Slog.w(TAG, "Detected encryption in progress - only parsing core apps"); onlyCore = true; @@ -244,6 +295,7 @@ class ServerThread extends Thread { Slog.i(TAG, "Window Manager"); wm = WindowManagerService.main(context, power, display, + uiHandler, wmHandler, factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL, !firstBoot, onlyCore); ServiceManager.addService(Context.WINDOW_SERVICE, wm); @@ -753,6 +805,12 @@ class ServerThread extends Thread { reportWtf("making Package Manager Service ready", e); } + try { + display.systemReady(safeMode, onlyCore); + } catch (Throwable e) { + reportWtf("making Display Manager Service ready", e); + } + // These are needed to propagate to the runnable below. final Context contextF = context; final BatteryService batteryF = battery; diff --git a/services/java/com/android/server/display/DisplayAdapter.java b/services/java/com/android/server/display/DisplayAdapter.java index f9fa7a871f21..d19fe017d6d3 100644 --- a/services/java/com/android/server/display/DisplayAdapter.java +++ b/services/java/com/android/server/display/DisplayAdapter.java @@ -16,33 +16,96 @@ package com.android.server.display; +import android.content.Context; +import android.os.Handler; + +import java.io.PrintWriter; + /** * A display adapter makes zero or more display devices available to the system * and provides facilities for discovering when displays are connected or disconnected. *

* For now, all display adapters are registered in the system server but * in principle it could be done from other processes. + *

+ * Display devices are not thread-safe and must only be accessed + * on the display manager service's handler thread. *

*/ -public abstract class DisplayAdapter { +public class DisplayAdapter { + private final Context mContext; + private final String mName; + private final Handler mHandler; + private Listener mListener; + + public static final int DISPLAY_DEVICE_EVENT_ADDED = 1; + public static final int DISPLAY_DEVICE_EVENT_CHANGED = 2; + public static final int DISPLAY_DEVICE_EVENT_REMOVED = 3; + + public DisplayAdapter(Context context, String name) { + mContext = context; + mName = name; + mHandler = new Handler(); + } + + public final Context getContext() { + return mContext; + } + + public final Handler getHandler() { + return mHandler; + } + /** * Gets the display adapter name for debugging purposes. * * @return The display adapter name. */ - public abstract String getName(); + public final String getName() { + return mName; + } /** * Registers the display adapter with the display manager. - * The display adapter should register any built-in display devices now. - * Other display devices can be registered dynamically later. * - * @param listener The listener for callbacks. + * @param listener The listener for callbacks. The listener will + * be invoked on the display manager service's handler thread. + */ + public final void register(Listener listener) { + mListener = listener; + onRegister(); + } + + /** + * Dumps the local state of the display adapter. + */ + public void dump(PrintWriter pw) { + } + + /** + * Called when the display adapter is registered. + * + * The display adapter should register any built-in display devices as soon as possible. + * The boot process will wait for the default display to be registered. + * + * Other display devices can be registered dynamically later. */ - public abstract void register(Listener listener); + protected void onRegister() { + } + + /** + * Sends a display device event to the display adapter listener asynchronously. + */ + protected void sendDisplayDeviceEvent(final DisplayDevice device, final int event) { + mHandler.post(new Runnable() { + @Override + public void run() { + mListener.onDisplayDeviceEvent(device, event); + } + }); + } public interface Listener { - public void onDisplayDeviceAdded(DisplayDevice device); - public void onDisplayDeviceRemoved(DisplayDevice device); + public void onDisplayDeviceEvent(DisplayDevice device, int event); } } diff --git a/services/java/com/android/server/display/DisplayDevice.java b/services/java/com/android/server/display/DisplayDevice.java index 4a6dd66e6134..c83ce9674324 100644 --- a/services/java/com/android/server/display/DisplayDevice.java +++ b/services/java/com/android/server/display/DisplayDevice.java @@ -21,14 +21,28 @@ import android.os.IBinder; /** * Represents a physical display device such as the built-in display * an external monitor, or a WiFi display. + *

+ * Display devices are not thread-safe and must only be accessed + * on the display manager service's handler thread. + *

*/ public abstract class DisplayDevice { + private final DisplayAdapter mDisplayAdapter; + private final IBinder mDisplayToken; + + public DisplayDevice(DisplayAdapter displayAdapter, IBinder displayToken) { + mDisplayAdapter = displayAdapter; + mDisplayToken = displayToken; + } + /** - * Gets the display adapter that makes the display device available. + * Gets the display adapter that owns the display device. * * @return The display adapter. */ - public abstract DisplayAdapter getAdapter(); + public final DisplayAdapter getAdapter() { + return mDisplayAdapter; + } /** * Gets the Surface Flinger display token for this display. @@ -36,7 +50,9 @@ public abstract class DisplayDevice { * @return The display token, or null if the display is not being managed * by Surface Flinger. */ - public abstract IBinder getDisplayToken(); + public final IBinder getDisplayToken() { + return mDisplayToken; + } /** * Gets information about the display device. @@ -44,4 +60,12 @@ public abstract class DisplayDevice { * @param outInfo The object to populate with the information. */ public abstract void getInfo(DisplayDeviceInfo outInfo); + + // For debugging purposes. + @Override + public String toString() { + DisplayDeviceInfo info = new DisplayDeviceInfo(); + getInfo(info); + return info.toString() + ", owner=\"" + mDisplayAdapter.getName() + "\""; + } } diff --git a/services/java/com/android/server/display/DisplayDeviceInfo.java b/services/java/com/android/server/display/DisplayDeviceInfo.java index 9c0f9644b1ed..c7b8c244e987 100644 --- a/services/java/com/android/server/display/DisplayDeviceInfo.java +++ b/services/java/com/android/server/display/DisplayDeviceInfo.java @@ -20,6 +20,9 @@ package com.android.server.display; * Describes the characteristics of a physical display device. */ public final class DisplayDeviceInfo { + public static final int FLAG_DEFAULT_DISPLAY = 1 << 0; + public static final int FLAG_SECURE = 1 << 1; + /** * Gets the name of the display device, which may be derived from * EDID or other sources. The name may be displayed to the user. @@ -43,6 +46,8 @@ public final class DisplayDeviceInfo { public float xDpi; public float yDpi; + public int flags; + public void copyFrom(DisplayDeviceInfo other) { name = other.name; width = other.width; @@ -51,12 +56,25 @@ public final class DisplayDeviceInfo { densityDpi = other.densityDpi; xDpi = other.xDpi; yDpi = other.yDpi; + flags = other.flags; } // For debugging purposes @Override public String toString() { return "\"" + name + "\": " + width + " x " + height + ", " + refreshRate + " fps, " - + "density " + densityDpi + ", " + xDpi + " x " + yDpi + " dpi"; + + "density " + densityDpi + ", " + xDpi + " x " + yDpi + " dpi" + + flagsToString(flags); + } + + private static String flagsToString(int flags) { + StringBuilder msg = new StringBuilder(); + if ((flags & FLAG_DEFAULT_DISPLAY) != 0) { + msg.append(", FLAG_DEFAULT_DISPLAY"); + } + if ((flags & FLAG_SECURE) != 0) { + msg.append(", FLAG_SECURE"); + } + return msg.toString(); } } diff --git a/services/java/com/android/server/display/DisplayManagerService.java b/services/java/com/android/server/display/DisplayManagerService.java index 1463780a3289..cf835ef209b4 100644 --- a/services/java/com/android/server/display/DisplayManagerService.java +++ b/services/java/com/android/server/display/DisplayManagerService.java @@ -16,52 +16,151 @@ package com.android.server.display; +import com.android.internal.util.IndentingPrintWriter; + import android.Manifest; import android.content.Context; import android.content.pm.PackageManager; -import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManagerGlobal; import android.hardware.display.IDisplayManager; +import android.hardware.display.IDisplayManagerCallback; import android.os.Binder; +import android.os.Handler; import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.os.SystemClock; import android.os.SystemProperties; +import android.util.Slog; +import android.util.SparseArray; import android.view.Display; import android.view.DisplayInfo; import android.view.Surface; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.io.StringWriter; import java.util.ArrayList; /** - * Manages the properties, media routing and power state of attached displays. + * Manages attached displays. *

- * The display manager service does not own or directly control the displays. - * Instead, other components in the system register their display adapters with the - * display manager service which acts as a central controller. + * The {@link DisplayManagerService} manages the global lifecycle of displays, + * decides how to configure logical displays based on the physical display devices currently + * attached, sends notifications to the system and to applications when the state + * changes, and so on. + *

+ * The display manager service relies on a collection of {@link DisplayAdapter} components, + * for discovering and configuring physical display devices attached to the system. + * There are separate display adapters for each manner that devices are attached: + * one display adapter for built-in local displays, one for simulated non-functional + * displays when the system is headless, one for simulated overlay displays used for + * development, one for wifi displays, etc. + *

+ * Display adapters are only weakly coupled to the display manager service. + * Display adapters communicate changes in display device state to the display manager + * service asynchronously via a {@link DisplayAdapter.DisplayAdapterListener} registered + * by the display manager service. This separation of concerns is important for + * two main reasons. First, it neatly encapsulates the responsibilities of these + * two classes: display adapters handle individual display devices whereas + * the display manager service handles the global state. Second, it eliminates + * the potential for deadlocks resulting from asynchronous display device discovery. + *

+ * To keep things simple, display adapters and display devices are single-threaded + * and are only accessed on the display manager's handler thread. Of course, the + * display manager must be accessible by multiple thread (especially for + * incoming binder calls) so all of the display manager's state is synchronized + * and guarded by a lock. *

*/ public final class DisplayManagerService extends IDisplayManager.Stub { private static final String TAG = "DisplayManagerService"; + private static final boolean DEBUG = false; private static final String SYSTEM_HEADLESS = "ro.config.headless"; + private static final long WAIT_FOR_DEFAULT_DISPLAY_TIMEOUT = 10000; + + private static final int MSG_REGISTER_DEFAULT_DISPLAY_ADAPTER = 1; + private static final int MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS = 2; + private static final int MSG_DELIVER_DISPLAY_EVENT = 3; private final Object mLock = new Object(); private final Context mContext; private final boolean mHeadless; + private final DisplayManagerHandler mHandler; + private final DisplayAdapterListener mDisplayAdapterListener = new DisplayAdapterListener(); + private final SparseArray mCallbacks = + new SparseArray(); + + // List of all currently registered display adapters. private final ArrayList mDisplayAdapters = new ArrayList(); - private final DisplayInfo mDefaultDisplayInfo = new DisplayInfo(); - private DisplayDevice mDefaultDisplayDevice; - public DisplayManagerService(Context context) { + // List of all currently connected display devices. + private final ArrayList mDisplayDevices = new ArrayList(); + + // List of all logical displays, indexed by logical display id. + private final SparseArray mLogicalDisplays = new SparseArray(); + private int mNextNonDefaultDisplayId = Display.DEFAULT_DISPLAY + 1; + + // True if in safe mode. + // This option may disable certain display adapters. + private boolean mSafeMode; + + // True if we are in a special boot mode where only core applications and + // services should be started. This option may disable certain display adapters. + private boolean mOnlyCore; + + // Temporary callback list, used when sending display events to applications. + private ArrayList mTempCallbacks = new ArrayList(); + + public DisplayManagerService(Context context, Handler uiHandler) { mContext = context; mHeadless = SystemProperties.get(SYSTEM_HEADLESS).equals("1"); - registerDefaultDisplayAdapter(); + mHandler = new DisplayManagerHandler(uiHandler.getLooper()); + mHandler.sendEmptyMessage(MSG_REGISTER_DEFAULT_DISPLAY_ADAPTER); + } + + /** + * Pauses the boot process to wait for the first display to be initialized. + */ + public boolean waitForDefaultDisplay() { + synchronized (mLock) { + long timeout = SystemClock.uptimeMillis() + WAIT_FOR_DEFAULT_DISPLAY_TIMEOUT; + while (mLogicalDisplays.get(Display.DEFAULT_DISPLAY) == null) { + long delay = timeout - SystemClock.uptimeMillis(); + if (delay <= 0) { + return false; + } + if (DEBUG) { + Slog.d(TAG, "waitForDefaultDisplay: waiting, timeout=" + delay); + } + try { + mLock.wait(delay); + } catch (InterruptedException ex) { + } + } + } + return true; + } + + /** + * Called when the system is ready to go. + */ + public void systemReady(boolean safeMode, boolean onlyCore) { + synchronized (mLock) { + mSafeMode = safeMode; + mOnlyCore = onlyCore; + } + mHandler.sendEmptyMessage(MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS); } + // Runs on handler. private void registerDefaultDisplayAdapter() { + // Register default display adapter. if (mHeadless) { registerDisplayAdapter(new HeadlessDisplayAdapter(mContext)); } else { @@ -69,6 +168,34 @@ public final class DisplayManagerService extends IDisplayManager.Stub { } } + // Runs on handler. + private void registerAdditionalDisplayAdapters() { + if (shouldRegisterNonEssentialDisplayAdapters()) { + registerDisplayAdapter(new OverlayDisplayAdapter(mContext)); + } + } + + private boolean shouldRegisterNonEssentialDisplayAdapters() { + // In safe mode, we disable non-essential display adapters to give the user + // an opportunity to fix broken settings or other problems that might affect + // system stability. + // In only-core mode, we disable non-essential display adapters to minimize + // the number of dependencies that are started while in this mode and to + // prevent problems that might occur due to the device being encrypted. + synchronized (mLock) { + return !mSafeMode && !mOnlyCore; + } + } + + // Runs on handler. + private void registerDisplayAdapter(DisplayAdapter adapter) { + synchronized (mLock) { + mDisplayAdapters.add(adapter); + } + + adapter.register(mDisplayAdapterListener); + } + // 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 @@ -87,95 +214,224 @@ public final class DisplayManagerService extends IDisplayManager.Stub { } /** - * Set the new display orientation. + * Sets the new logical display orientation. + * * @param displayId The logical display id. * @param orientation One of the Surface.ROTATION_* constants. */ public void setDisplayOrientation(int displayId, int orientation) { synchronized (mLock) { - if (displayId != Display.DEFAULT_DISPLAY) { - throw new UnsupportedOperationException(); - } - - IBinder displayToken = mDefaultDisplayDevice.getDisplayToken(); - if (displayToken != null) { - Surface.openTransaction(); - try { - Surface.setDisplayOrientation(displayToken, orientation); - } finally { - Surface.closeTransaction(); + // TODO: update mirror transforms + LogicalDisplay display = mLogicalDisplays.get(displayId); + if (display != null && display.mPrimaryDisplayDevice != null) { + IBinder displayToken = display.mPrimaryDisplayDevice.getDisplayToken(); + if (displayToken != null) { + Surface.openTransaction(); + try { + Surface.setDisplayOrientation(displayToken, orientation); + } finally { + Surface.closeTransaction(); + } } + + display.mBaseDisplayInfo.rotation = orientation; + sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED); } } } /** - * Save away new DisplayInfo data. + * Overrides the display information of a particular logical display. + * This is used by the window manager to control the size and characteristics + * of the default display. + * * @param displayId The logical display id. * @param info The new data to be stored. */ - public void setDisplayInfo(int displayId, DisplayInfo info) { + public void setDisplayInfoOverrideFromWindowManager(int displayId, DisplayInfo info) { synchronized (mLock) { - if (displayId != Display.DEFAULT_DISPLAY) { - throw new UnsupportedOperationException(); + LogicalDisplay display = mLogicalDisplays.get(displayId); + if (display != null) { + if (info != null) { + if (display.mOverrideDisplayInfo == null) { + display.mOverrideDisplayInfo = new DisplayInfo(); + } + display.mOverrideDisplayInfo.copyFrom(info); + } else { + display.mOverrideDisplayInfo = null; + } + + sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED); } - mDefaultDisplayInfo.copyFrom(info); } } /** - * Return requested DisplayInfo. - * @param displayId The data to retrieve. - * @param outInfo The structure to receive the data. + * Returns information about the specified logical display. + * + * @param displayId The logical display id. + * @param The logical display info, or null if the display does not exist. */ @Override // Binder call - public boolean getDisplayInfo(int displayId, DisplayInfo outInfo) { + public DisplayInfo getDisplayInfo(int displayId) { synchronized (mLock) { - if (displayId != Display.DEFAULT_DISPLAY) { - return false; + LogicalDisplay display = mLogicalDisplays.get(displayId); + if (display != null) { + if (display.mOverrideDisplayInfo != null) { + return new DisplayInfo(display.mOverrideDisplayInfo); + } + return new DisplayInfo(display.mBaseDisplayInfo); } - outInfo.copyFrom(mDefaultDisplayInfo); - return true; + return null; } } - private void registerDisplayAdapter(DisplayAdapter adapter) { - mDisplayAdapters.add(adapter); - adapter.register(new DisplayAdapter.Listener() { - @Override - public void onDisplayDeviceAdded(DisplayDevice device) { - mDefaultDisplayDevice = device; - DisplayDeviceInfo deviceInfo = new DisplayDeviceInfo(); - device.getInfo(deviceInfo); - copyDisplayInfoFromDeviceInfo(mDefaultDisplayInfo, deviceInfo); + @Override // Binder call + public int[] getDisplayIds() { + synchronized (mLock) { + final int count = mLogicalDisplays.size(); + int[] displayIds = new int[count]; + for (int i = 0; i > count; i++) { + displayIds[i] = mLogicalDisplays.keyAt(i); } + return displayIds; + } + } - @Override - public void onDisplayDeviceRemoved(DisplayDevice device) { + @Override // Binder call + public void registerCallback(IDisplayManagerCallback callback) { + if (callback == null) { + throw new IllegalArgumentException("listener must not be null"); + } + + synchronized (mLock) { + int callingPid = Binder.getCallingPid(); + if (mCallbacks.get(callingPid) != null) { + throw new SecurityException("The calling process has already " + + "registered an IDisplayManagerCallback."); } - }); + + CallbackRecord record = new CallbackRecord(callingPid, callback); + try { + IBinder binder = callback.asBinder(); + binder.linkToDeath(record, 0); + } catch (RemoteException ex) { + // give up + throw new RuntimeException(ex); + } + + mCallbacks.put(callingPid, record); + } } - 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; + private void onCallbackDied(int pid) { + synchronized (mLock) { + mCallbacks.remove(pid); + } + } + + // Runs on handler. + private void handleDisplayDeviceAdded(DisplayDevice device) { + synchronized (mLock) { + if (mDisplayDevices.contains(device)) { + Slog.w(TAG, "Attempted to add already added display device: " + device); + return; + } + + mDisplayDevices.add(device); + + LogicalDisplay display = new LogicalDisplay(device); + display.updateFromPrimaryDisplayDevice(); + + boolean isDefault = (display.mPrimaryDisplayDeviceInfo.flags + & DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY) != 0; + if (isDefault && mLogicalDisplays.get(Display.DEFAULT_DISPLAY) != null) { + Slog.w(TAG, "Attempted to add a second default device: " + device); + isDefault = false; + } + + int displayId = isDefault ? Display.DEFAULT_DISPLAY : mNextNonDefaultDisplayId++; + mLogicalDisplays.put(displayId, display); + + sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED); + + // Wake up waitForDefaultDisplay. + if (isDefault) { + mLock.notifyAll(); + } + } + } + + // Runs on handler. + private void handleDisplayDeviceChanged(DisplayDevice device) { + synchronized (mLock) { + if (!mDisplayDevices.contains(device)) { + Slog.w(TAG, "Attempted to change non-existent display device: " + device); + return; + } + + for (int i = mLogicalDisplays.size(); i-- > 0; ) { + LogicalDisplay display = mLogicalDisplays.valueAt(i); + if (display.mPrimaryDisplayDevice == device) { + final int displayId = mLogicalDisplays.keyAt(i); + display.updateFromPrimaryDisplayDevice(); + sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED); + } + } + } + } + + // Runs on handler. + private void handleDisplayDeviceRemoved(DisplayDevice device) { + synchronized (mLock) { + if (!mDisplayDevices.remove(device)) { + Slog.w(TAG, "Attempted to remove non-existent display device: " + device); + return; + } + + for (int i = mLogicalDisplays.size(); i-- > 0; ) { + LogicalDisplay display = mLogicalDisplays.valueAt(i); + if (display.mPrimaryDisplayDevice == device) { + final int displayId = mLogicalDisplays.keyAt(i); + mLogicalDisplays.removeAt(i); + sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED); + } + } + } + } + + // Posts a message to send a display event at the next opportunity. + private void sendDisplayEventLocked(int displayId, int event) { + Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_EVENT, displayId, event); + mHandler.sendMessage(msg); + } + + // Runs on handler. + // This method actually sends display event notifications. + // Note that it must be very careful not to be holding the lock while sending + // is in progress. + private void deliverDisplayEvent(int displayId, int event) { + if (DEBUG) { + Slog.d(TAG, "Delivering display event: displayId=" + displayId + ", event=" + event); + } + + final int count; + synchronized (mLock) { + count = mCallbacks.size(); + mTempCallbacks.clear(); + for (int i = 0; i < count; i++) { + mTempCallbacks.add(mCallbacks.valueAt(i)); + } + } + + for (int i = 0; i < count; i++) { + mTempCallbacks.get(i).notifyDisplayEventAsync(displayId, event); + } + mTempCallbacks.clear(); } @Override // Binder call - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { if (mContext == null || mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) { @@ -184,19 +440,159 @@ public final class DisplayManagerService extends IDisplayManager.Stub { return; } - pw.println("DISPLAY MANAGER (dumpsys display)\n"); + pw.println("DISPLAY MANAGER (dumpsys display)"); + pw.println(" mHeadless=" + mHeadless); - pw.println("Headless: " + mHeadless); + mHandler.runWithScissors(new Runnable() { + @Override + public void run() { + dumpLocal(pw); + } + }); + } + // Runs on handler. + private void dumpLocal(PrintWriter pw) { synchronized (mLock) { + IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); + + pw.println(); + pw.println("Display Adapters: size=" + mDisplayAdapters.size()); for (DisplayAdapter adapter : mDisplayAdapters) { - pw.println("Adapter: " + adapter.getName()); + pw.println(" " + adapter.getName()); + adapter.dump(ipw); } - pw.println("Default display info: " + mDefaultDisplayInfo); + pw.println(); + pw.println("Display Devices: size=" + mDisplayDevices.size()); + for (DisplayDevice device : mDisplayDevices) { + pw.println(" " + device); + } + + final int logicalDisplayCount = mLogicalDisplays.size(); + pw.println(); + pw.println("Logical Displays: size=" + logicalDisplayCount); + for (int i = 0; i < logicalDisplayCount; i++) { + int displayId = mLogicalDisplays.keyAt(i); + LogicalDisplay display = mLogicalDisplays.valueAt(i); + pw.println(" Display " + displayId + ":"); + pw.println(" mPrimaryDisplayDevice=" + display.mPrimaryDisplayDevice); + pw.println(" mBaseDisplayInfo=" + display.mBaseDisplayInfo); + pw.println(" mOverrideDisplayInfo=" + + display.mOverrideDisplayInfo); + } + } + } + + private final class DisplayManagerHandler extends Handler { + public DisplayManagerHandler(Looper looper) { + super(looper, null, true /*async*/); } - pw.println("Default display: " - + DisplayManager.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY)); + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_REGISTER_DEFAULT_DISPLAY_ADAPTER: + registerDefaultDisplayAdapter(); + break; + + case MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS: + registerAdditionalDisplayAdapters(); + break; + + case MSG_DELIVER_DISPLAY_EVENT: + deliverDisplayEvent(msg.arg1, msg.arg2); + break; + } + } + } + + private final class DisplayAdapterListener implements DisplayAdapter.Listener { + @Override + public void onDisplayDeviceEvent(DisplayDevice device, int event) { + switch (event) { + case DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED: + handleDisplayDeviceAdded(device); + break; + + case DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED: + handleDisplayDeviceChanged(device); + break; + + case DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED: + handleDisplayDeviceRemoved(device); + break; + } + } + } + + private final class CallbackRecord implements DeathRecipient { + private final int mPid; + private final IDisplayManagerCallback mCallback; + + public CallbackRecord(int pid, IDisplayManagerCallback callback) { + mPid = pid; + mCallback = callback; + } + + @Override + public void binderDied() { + if (DEBUG) { + Slog.d(TAG, "Display listener for pid " + mPid + " died."); + } + onCallbackDied(mPid); + } + + public void notifyDisplayEventAsync(int displayId, int event) { + try { + mCallback.onDisplayEvent(displayId, event); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to notify process " + + mPid + " that displays changed, assuming it died.", ex); + binderDied(); + } + } + } + + /** + * Each logical display is primarily associated with one display device. + * The primary display device is nominally responsible for the basic properties + * of the logical display such as its size, refresh rate, and dpi. + * + * A logical display may be mirrored onto other display devices besides its + * primary display device, but it always remains bound to its primary. + * Note that the contents of a logical display may not always be visible, even + * on its primary display device, such as in the case where the logical display's + * primary display device is currently mirroring content from a different logical display. + */ + private final static class LogicalDisplay { + public final DisplayInfo mBaseDisplayInfo = new DisplayInfo(); + public DisplayInfo mOverrideDisplayInfo; // set by the window manager + + public final DisplayDevice mPrimaryDisplayDevice; + public final DisplayDeviceInfo mPrimaryDisplayDeviceInfo = new DisplayDeviceInfo(); + + public LogicalDisplay(DisplayDevice primaryDisplayDevice) { + mPrimaryDisplayDevice = primaryDisplayDevice; + } + + public void updateFromPrimaryDisplayDevice() { + // Bootstrap the logical display using its associated primary physical display. + mPrimaryDisplayDevice.getInfo(mPrimaryDisplayDeviceInfo); + + mBaseDisplayInfo.appWidth = mPrimaryDisplayDeviceInfo.width; + mBaseDisplayInfo.appHeight = mPrimaryDisplayDeviceInfo.height; + mBaseDisplayInfo.logicalWidth = mPrimaryDisplayDeviceInfo.width; + mBaseDisplayInfo.logicalHeight = mPrimaryDisplayDeviceInfo.height; + mBaseDisplayInfo.rotation = Surface.ROTATION_0; + mBaseDisplayInfo.refreshRate = mPrimaryDisplayDeviceInfo.refreshRate; + mBaseDisplayInfo.logicalDensityDpi = mPrimaryDisplayDeviceInfo.densityDpi; + mBaseDisplayInfo.physicalXDpi = mPrimaryDisplayDeviceInfo.xDpi; + mBaseDisplayInfo.physicalYDpi = mPrimaryDisplayDeviceInfo.yDpi; + mBaseDisplayInfo.smallestNominalAppWidth = mPrimaryDisplayDeviceInfo.width; + mBaseDisplayInfo.smallestNominalAppHeight = mPrimaryDisplayDeviceInfo.height; + mBaseDisplayInfo.largestNominalAppWidth = mPrimaryDisplayDeviceInfo.width; + mBaseDisplayInfo.largestNominalAppHeight = mPrimaryDisplayDeviceInfo.height; + } } } diff --git a/services/java/com/android/server/display/HeadlessDisplayAdapter.java b/services/java/com/android/server/display/HeadlessDisplayAdapter.java index f984c5d90e37..f5c78b9f4930 100644 --- a/services/java/com/android/server/display/HeadlessDisplayAdapter.java +++ b/services/java/com/android/server/display/HeadlessDisplayAdapter.java @@ -17,52 +17,44 @@ package com.android.server.display; import android.content.Context; -import android.os.IBinder; import android.util.DisplayMetrics; /** * Provides a fake default display for headless systems. + *

+ * Display adapters are not thread-safe and must only be accessed + * on the display manager service's handler thread. + *

*/ public final class HeadlessDisplayAdapter extends DisplayAdapter { - private final Context mContext; - private final HeadlessDisplayDevice mDefaultDisplayDevice; + private static final String TAG = "HeadlessDisplayAdapter"; public HeadlessDisplayAdapter(Context context) { - mContext = context; - mDefaultDisplayDevice = new HeadlessDisplayDevice(); + super(context, TAG); } @Override - public String getName() { - return "HeadlessDisplayAdapter"; - } - - @Override - public void register(Listener listener) { - listener.onDisplayDeviceAdded(mDefaultDisplayDevice); + protected void onRegister() { + sendDisplayDeviceEvent(new HeadlessDisplayDevice(), DISPLAY_DEVICE_EVENT_ADDED); } private final class HeadlessDisplayDevice extends DisplayDevice { - @Override - public DisplayAdapter getAdapter() { - return HeadlessDisplayAdapter.this; - } - - @Override - public IBinder getDisplayToken() { - return null; + public HeadlessDisplayDevice() { + super(HeadlessDisplayAdapter.this, null); } @Override public void getInfo(DisplayDeviceInfo outInfo) { - outInfo.name = mContext.getResources().getString( - com.android.internal.R.string.display_manager_built_in_display); + outInfo.name = getContext().getResources().getString( + com.android.internal.R.string.display_manager_built_in_display_name); outInfo.width = 640; outInfo.height = 480; outInfo.refreshRate = 60; outInfo.densityDpi = DisplayMetrics.DENSITY_DEFAULT; outInfo.xDpi = 160; outInfo.yDpi = 160; + outInfo.flags = DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY + | DisplayDeviceInfo.FLAG_SECURE; } } } diff --git a/services/java/com/android/server/display/LocalDisplayAdapter.java b/services/java/com/android/server/display/LocalDisplayAdapter.java index fa778786c06b..73544fc1fa9f 100644 --- a/services/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/java/com/android/server/display/LocalDisplayAdapter.java @@ -23,58 +23,52 @@ import android.view.Surface.PhysicalDisplayInfo; /** * A display adapter for the local displays managed by Surface Flinger. + *

+ * Display adapters are not thread-safe and must only be accessed + * on the display manager service's handler thread. + *

*/ public final class LocalDisplayAdapter extends DisplayAdapter { - private final Context mContext; - private final LocalDisplayDevice mDefaultDisplayDevice; + private static final String TAG = "LocalDisplayAdapter"; public LocalDisplayAdapter(Context context) { - mContext = context; - - IBinder token = Surface.getBuiltInDisplay(Surface.BUILT_IN_DISPLAY_ID_MAIN); - mDefaultDisplayDevice = new LocalDisplayDevice(token); + super(context, TAG); } @Override - public String getName() { - return "LocalDisplayAdapter"; - } - - @Override - public void register(Listener listener) { - listener.onDisplayDeviceAdded(mDefaultDisplayDevice); + protected void onRegister() { + // TODO: listen for notifications from Surface Flinger about + // built-in displays being added or removed and rescan as needed. + IBinder displayToken = Surface.getBuiltInDisplay(Surface.BUILT_IN_DISPLAY_ID_MAIN); + sendDisplayDeviceEvent(new LocalDisplayDevice(displayToken, true), + DISPLAY_DEVICE_EVENT_ADDED); } private final class LocalDisplayDevice extends DisplayDevice { - private final IBinder mDisplayToken; - - public LocalDisplayDevice(IBinder token) { - mDisplayToken = token; - } + private final boolean mIsDefault; - @Override - public DisplayAdapter getAdapter() { - return LocalDisplayAdapter.this; - } - - @Override - public IBinder getDisplayToken() { - return mDisplayToken; + public LocalDisplayDevice(IBinder displayToken, boolean isDefault) { + super(LocalDisplayAdapter.this, displayToken); + mIsDefault = isDefault; } @Override public void getInfo(DisplayDeviceInfo outInfo) { PhysicalDisplayInfo phys = new PhysicalDisplayInfo(); - Surface.getDisplayInfo(mDisplayToken, phys); + Surface.getDisplayInfo(getDisplayToken(), phys); - outInfo.name = mContext.getResources().getString( - com.android.internal.R.string.display_manager_built_in_display); + outInfo.name = getContext().getResources().getString( + com.android.internal.R.string.display_manager_built_in_display_name); outInfo.width = phys.width; outInfo.height = phys.height; outInfo.refreshRate = phys.refreshRate; outInfo.densityDpi = (int)(phys.density * 160 + 0.5f); outInfo.xDpi = phys.xDpi; outInfo.yDpi = phys.yDpi; + if (mIsDefault) { + outInfo.flags = DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY + | DisplayDeviceInfo.FLAG_SECURE; + } } } } diff --git a/services/java/com/android/server/display/OverlayDisplayAdapter.java b/services/java/com/android/server/display/OverlayDisplayAdapter.java new file mode 100644 index 000000000000..476d21ab08e1 --- /dev/null +++ b/services/java/com/android/server/display/OverlayDisplayAdapter.java @@ -0,0 +1,530 @@ +/* + * Copyright (C) 2012 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.display; + +import android.content.Context; +import android.database.ContentObserver; +import android.graphics.SurfaceTexture; +import android.hardware.display.DisplayManager; +import android.os.IBinder; +import android.provider.Settings; +import android.util.DisplayMetrics; +import android.util.Slog; +import android.view.Display; +import android.view.DisplayInfo; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.ScaleGestureDetector; +import android.view.Surface; +import android.view.TextureView; +import android.view.TextureView.SurfaceTextureListener; +import android.view.View; +import android.view.WindowManager; +import android.widget.TextView; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A display adapter that uses overlay windows to simulate secondary displays + * for development purposes. Use Development Settings to enable one or more + * overlay displays. + *

+ * Display adapters are not thread-safe and must only be accessed + * on the display manager service's handler thread. + *

+ */ +public final class OverlayDisplayAdapter extends DisplayAdapter { + private static final String TAG = "OverlayDisplayAdapter"; + + private static final int MIN_WIDTH = 100; + private static final int MIN_HEIGHT = 100; + private static final int MAX_WIDTH = 4096; + private static final int MAX_HEIGHT = 4096; + + private static final Pattern SETTING_PATTERN = + Pattern.compile("(\\d+)x(\\d+)/(\\d+)"); + + private final ArrayList mOverlays = new ArrayList(); + private String mCurrentOverlaySetting = ""; + + public OverlayDisplayAdapter(Context context) { + super(context, TAG); + } + + @Override + public void dump(PrintWriter pw) { + pw.println("mCurrentOverlaySetting=" + mCurrentOverlaySetting); + pw.println("mOverlays: size=" + mOverlays.size()); + for (Overlay overlay : mOverlays) { + overlay.dump(pw); + } + } + + @Override + protected void onRegister() { + getContext().getContentResolver().registerContentObserver( + Settings.System.getUriFor(Settings.Secure.OVERLAY_DISPLAY_DEVICES), true, + new ContentObserver(getHandler()) { + @Override + public void onChange(boolean selfChange) { + updateOverlayDisplayDevices(); + } + }); + updateOverlayDisplayDevices(); + } + + private void updateOverlayDisplayDevices() { + String value = Settings.System.getString(getContext().getContentResolver(), + Settings.Secure.OVERLAY_DISPLAY_DEVICES); + if (value == null) { + value = ""; + } + + if (value.equals(mCurrentOverlaySetting)) { + return; + } + mCurrentOverlaySetting = value; + + if (!mOverlays.isEmpty()) { + Slog.i(TAG, "Dismissing all overlay display devices."); + for (Overlay overlay : mOverlays) { + overlay.dismiss(); + } + mOverlays.clear(); + } + + int number = 1; + for (String part : value.split(";")) { + if (number > 4) { + Slog.w(TAG, "Too many overlay display devices."); + } + Matcher matcher = SETTING_PATTERN.matcher(part); + if (matcher.matches()) { + try { + int width = Integer.parseInt(matcher.group(1), 10); + int height = Integer.parseInt(matcher.group(2), 10); + int densityDpi = Integer.parseInt(matcher.group(3), 10); + if (width >= MIN_WIDTH && width <= MAX_WIDTH + && height >= MIN_HEIGHT && height <= MAX_HEIGHT + && densityDpi >= DisplayMetrics.DENSITY_LOW + && densityDpi <= DisplayMetrics.DENSITY_XXHIGH) { + Slog.i(TAG, "Showing overlay display device #" + number + + ": width=" + width + ", height=" + height + + ", densityDpi=" + densityDpi); + mOverlays.add(new Overlay(number++, width, height, densityDpi)); + continue; + } + } catch (NumberFormatException ex) { + } + } else if (part.isEmpty()) { + continue; + } + Slog.w(TAG, "Malformed overlay display devices setting: \"" + value + "\""); + } + + for (Overlay overlay : mOverlays) { + overlay.show(); + } + } + + // Manages an overlay window. + private final class Overlay { + private final float INITIAL_SCALE = 0.5f; + private final float MIN_SCALE = 0.3f; + private final float MAX_SCALE = 1.0f; + private final float WINDOW_ALPHA = 0.8f; + + // When true, disables support for moving and resizing the overlay. + // The window is made non-touchable, which makes it possible to + // directly interact with the content underneath. + private final boolean DISABLE_MOVE_AND_RESIZE = false; + + private final DisplayManager mDisplayManager; + private final WindowManager mWindowManager; + + private final int mNumber; + private final int mWidth; + private final int mHeight; + private final int mDensityDpi; + + private final String mName; + private final String mTitle; + + private final Display mDefaultDisplay; + private final DisplayInfo mDefaultDisplayInfo = new DisplayInfo(); + private final IBinder mDisplayToken; + private final OverlayDisplayDevice mDisplayDevice; + + private View mWindowContent; + private WindowManager.LayoutParams mWindowParams; + private TextureView mTextureView; + private TextView mTitleTextView; + private ScaleGestureDetector mScaleGestureDetector; + + private boolean mWindowVisible; + private int mWindowX; + private int mWindowY; + private float mWindowScale; + + private int mLiveTranslationX; + private int mLiveTranslationY; + private float mLiveScale = 1.0f; + + private int mDragPointerId; + private float mDragTouchX; + private float mDragTouchY; + + public Overlay(int number, int width, int height, int densityDpi) { + Context context = getContext(); + mDisplayManager = (DisplayManager)context.getSystemService( + Context.DISPLAY_SERVICE); + mWindowManager = (WindowManager)context.getSystemService( + Context.WINDOW_SERVICE); + + mNumber = number; + mWidth = width; + mHeight = height; + mDensityDpi = densityDpi; + + mName = context.getResources().getString( + com.android.internal.R.string.display_manager_overlay_display_name, number); + mTitle = context.getResources().getString( + com.android.internal.R.string.display_manager_overlay_display_title, + mNumber, mWidth, mHeight, mDensityDpi); + + mDefaultDisplay = mWindowManager.getDefaultDisplay(); + updateDefaultDisplayInfo(); + + mDisplayToken = Surface.createDisplay(mName); + mDisplayDevice = new OverlayDisplayDevice(mDisplayToken, mName, + mDefaultDisplayInfo.refreshRate, mDensityDpi); + + createWindow(); + } + + public void show() { + if (!mWindowVisible) { + mDisplayManager.registerDisplayListener(mDisplayListener, null); + if (!updateDefaultDisplayInfo()) { + mDisplayManager.unregisterDisplayListener(mDisplayListener); + return; + } + + clearLiveState(); + updateWindowParams(); + mWindowManager.addView(mWindowContent, mWindowParams); + mWindowVisible = true; + } + } + + public void dismiss() { + if (mWindowVisible) { + mDisplayManager.unregisterDisplayListener(mDisplayListener); + mWindowManager.removeView(mWindowContent); + mWindowVisible = false; + } + } + + public void relayout() { + if (mWindowVisible) { + updateWindowParams(); + mWindowManager.updateViewLayout(mWindowContent, mWindowParams); + } + } + + public void dump(PrintWriter pw) { + pw.println(" #" + mNumber + ": " + + mWidth + "x" + mHeight + ", " + mDensityDpi + " dpi"); + pw.println(" mName=" + mName); + pw.println(" mWindowVisible=" + mWindowVisible); + pw.println(" mWindowX=" + mWindowX); + pw.println(" mWindowY=" + mWindowY); + pw.println(" mWindowScale=" + mWindowScale); + pw.println(" mWindowParams=" + mWindowParams); + pw.println(" mLiveTranslationX=" + mLiveTranslationX); + pw.println(" mLiveTranslationY=" + mLiveTranslationY); + pw.println(" mLiveScale=" + mLiveScale); + } + + private boolean updateDefaultDisplayInfo() { + if (!mDefaultDisplay.getDisplayInfo(mDefaultDisplayInfo)) { + Slog.w(TAG, "Cannot show overlay display because there is no " + + "default display upon which to show it."); + return false; + } + return true; + } + + private void createWindow() { + Context context = getContext(); + LayoutInflater inflater = LayoutInflater.from(context); + + mWindowContent = inflater.inflate( + com.android.internal.R.layout.overlay_display_window, null); + mWindowContent.setOnTouchListener(mOnTouchListener); + + mTextureView = (TextureView)mWindowContent.findViewById( + com.android.internal.R.id.overlay_display_window_texture); + mTextureView.setPivotX(0); + mTextureView.setPivotY(0); + mTextureView.getLayoutParams().width = mWidth; + mTextureView.getLayoutParams().height = mHeight; + mTextureView.setSurfaceTextureListener(mSurfaceTextureListener); + + mTitleTextView = (TextView)mWindowContent.findViewById( + com.android.internal.R.id.overlay_display_window_title); + mTitleTextView.setText(mTitle); + + mWindowParams = new WindowManager.LayoutParams( + WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY); + mWindowParams.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; + if (DISABLE_MOVE_AND_RESIZE) { + mWindowParams.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; + } + mWindowParams.privateFlags |= + WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED; + mWindowParams.alpha = WINDOW_ALPHA; + mWindowParams.gravity = Gravity.TOP | Gravity.LEFT; + mWindowParams.setTitle(mTitle); + + mScaleGestureDetector = new ScaleGestureDetector(context, mOnScaleGestureListener); + + // By default, arrange the displays in the four corners. + mWindowVisible = false; + mWindowScale = INITIAL_SCALE; + if (mNumber == 2 || mNumber == 3) { + mWindowX = mDefaultDisplayInfo.logicalWidth; + } else { + mWindowX = 0; + } + if (mNumber == 2 || mNumber == 4) { + mWindowY = mDefaultDisplayInfo.logicalHeight; + } else { + mWindowY = 0; + } + } + + private void updateWindowParams() { + float scale = mWindowScale * mLiveScale; + if (mWidth * scale > mDefaultDisplayInfo.logicalWidth) { + scale = mDefaultDisplayInfo.logicalWidth / mWidth; + } + if (mHeight * scale > mDefaultDisplayInfo.logicalHeight) { + scale = mDefaultDisplayInfo.logicalHeight / mHeight; + } + scale = Math.max(MIN_SCALE, Math.min(MAX_SCALE, scale)); + + float offsetScale = (scale / mWindowScale - 1.0f) * 0.5f; + int width = (int)(mWidth * scale); + int height = (int)(mHeight * scale); + int x = mWindowX + mLiveTranslationX - (int)(width * offsetScale); + int y = mWindowY + mLiveTranslationY - (int)(height * offsetScale); + x = Math.max(0, Math.min(x, mDefaultDisplayInfo.logicalWidth - width)); + y = Math.max(0, Math.min(y, mDefaultDisplayInfo.logicalHeight - height)); + + mTextureView.setScaleX(scale); + mTextureView.setScaleY(scale); + + mWindowParams.x = x; + mWindowParams.y = y; + mWindowParams.width = width; + mWindowParams.height = height; + } + + private void saveWindowParams() { + mWindowX = mWindowParams.x; + mWindowY = mWindowParams.y; + mWindowScale = mTextureView.getScaleX(); + clearLiveState(); + } + + private void clearLiveState() { + mLiveTranslationX = 0; + mLiveTranslationY = 0; + mLiveScale = 1.0f; + } + + private final DisplayManager.DisplayListener mDisplayListener = + new DisplayManager.DisplayListener() { + @Override + public void onDisplayAdded(int displayId) { + } + + @Override + public void onDisplayChanged(int displayId) { + if (displayId == mDefaultDisplay.getDisplayId()) { + if (updateDefaultDisplayInfo()) { + relayout(); + } else { + dismiss(); + } + } + } + + @Override + public void onDisplayRemoved(int displayId) { + if (displayId == mDefaultDisplay.getDisplayId()) { + dismiss(); + } + } + }; + + private final SurfaceTextureListener mSurfaceTextureListener = + new SurfaceTextureListener() { + @Override + public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { + Surface.openTransaction(); + try { + Surface.setDisplaySurface(mDisplayToken, surface); + } finally { + Surface.closeTransaction(); + } + + mDisplayDevice.setSize(width, height); + sendDisplayDeviceEvent(mDisplayDevice, DISPLAY_DEVICE_EVENT_ADDED); + } + + @Override + public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { + sendDisplayDeviceEvent(mDisplayDevice, DISPLAY_DEVICE_EVENT_REMOVED); + + Surface.openTransaction(); + try { + Surface.setDisplaySurface(mDisplayToken, null); + } finally { + Surface.closeTransaction(); + } + return true; + } + + @Override + public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { + mDisplayDevice.setSize(width, height); + sendDisplayDeviceEvent(mDisplayDevice, DISPLAY_DEVICE_EVENT_CHANGED); + } + + @Override + public void onSurfaceTextureUpdated(SurfaceTexture surface) { + } + }; + + private final View.OnTouchListener mOnTouchListener = new View.OnTouchListener() { + @Override + public boolean onTouch(View view, MotionEvent event) { + // Work in screen coordinates. + final float oldX = event.getX(); + final float oldY = event.getY(); + event.setLocation(event.getRawX(), event.getRawY()); + + mScaleGestureDetector.onTouchEvent(event); + + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + resetDrag(event); + break; + + case MotionEvent.ACTION_MOVE: + if (event.getPointerCount() == 1) { + int index = event.findPointerIndex(mDragPointerId); + if (index < 0) { + resetDrag(event); + } else { + mLiveTranslationX = (int)(event.getX(index) - mDragTouchX); + mLiveTranslationY = (int)(event.getY(index) - mDragTouchY); + relayout(); + } + } + break; + + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + saveWindowParams(); + break; + } + + // Revert to window coordinates. + event.setLocation(oldX, oldY); + return true; + } + + private void resetDrag(MotionEvent event) { + saveWindowParams(); + mDragPointerId = event.getPointerId(0); + mDragTouchX = event.getX(); + mDragTouchY = event.getY(); + } + }; + + private final ScaleGestureDetector.OnScaleGestureListener mOnScaleGestureListener = + new ScaleGestureDetector.SimpleOnScaleGestureListener() { + @Override + public boolean onScaleBegin(ScaleGestureDetector detector) { + saveWindowParams(); + mDragPointerId = -1; // cause drag to be reset + return true; + } + + @Override + public boolean onScale(ScaleGestureDetector detector) { + mLiveScale = detector.getScaleFactor(); + relayout(); + return false; + } + }; + } + + private final class OverlayDisplayDevice extends DisplayDevice { + private final String mName; + private final float mRefreshRate; + private final int mDensityDpi; + private int mWidth; + private int mHeight; + + public OverlayDisplayDevice(IBinder displayToken, String name, + float refreshRate, int densityDpi) { + super(OverlayDisplayAdapter.this, displayToken); + mName = name; + mRefreshRate = refreshRate; + mDensityDpi = densityDpi; + } + + public void setSize(int width, int height) { + mWidth = width; + mHeight = height; + } + + @Override + public void getInfo(DisplayDeviceInfo outInfo) { + outInfo.name = mName; + outInfo.width = mWidth; + outInfo.height = mHeight; + outInfo.refreshRate = mRefreshRate; + outInfo.densityDpi = mDensityDpi; + outInfo.xDpi = mDensityDpi; + outInfo.yDpi = mDensityDpi; + outInfo.flags = DisplayDeviceInfo.FLAG_SECURE; + } + } +} diff --git a/services/java/com/android/server/power/DisplayPowerController.java b/services/java/com/android/server/power/DisplayPowerController.java index cd211da09973..6b6d8994eec6 100644 --- a/services/java/com/android/server/power/DisplayPowerController.java +++ b/services/java/com/android/server/power/DisplayPowerController.java @@ -45,7 +45,6 @@ import android.view.Display; import java.io.PrintWriter; import java.io.StringWriter; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; /** @@ -169,6 +168,9 @@ final class DisplayPowerController { // The twilight service. private final TwilightService mTwilight; + // The display manager. + private final DisplayManager mDisplayManager; + // The sensor manager. private final SensorManager mSensorManager; @@ -330,6 +332,7 @@ final class DisplayPowerController { mLights = lights; mTwilight = twilight; mSensorManager = new SystemSensorManager(mHandler.getLooper()); + mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE); final Resources resources = context.getResources(); mScreenBrightnessDimConfig = resources.getInteger( @@ -475,7 +478,7 @@ final class DisplayPowerController { private void initialize() { final Executor executor = AsyncTask.THREAD_POOL_EXECUTOR; - Display display = DisplayManager.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY); + Display display = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY); mPowerState = new DisplayPowerState(new ElectronBeam(display), new PhotonicModulator(executor, mLights.getLight(LightsService.LIGHT_ID_BACKLIGHT), @@ -980,7 +983,7 @@ final class DisplayPowerController { } }; - public void dump(PrintWriter pw) { + public void dump(final PrintWriter pw) { synchronized (mLock) { pw.println(); pw.println("Display Controller Locked State:"); @@ -1000,33 +1003,12 @@ final class DisplayPowerController { pw.println(" mScreenAutoBrightnessSpline=" + mScreenAutoBrightnessSpline); pw.println(" mLightSensorWarmUpTimeConfig=" + mLightSensorWarmUpTimeConfig); - if (Looper.myLooper() == mHandler.getLooper()) { - dumpLocal(pw); - } else { - final StringWriter out = new StringWriter(); - final CountDownLatch latch = new CountDownLatch(1); - Message msg = Message.obtain(mHandler, new Runnable() { - @Override - public void run() { - PrintWriter localpw = new PrintWriter(out); - try { - dumpLocal(localpw); - } finally { - localpw.flush(); - latch.countDown(); - } - } - }); - msg.setAsynchronous(true); - mHandler.sendMessage(msg); - try { - latch.await(); - pw.print(out.toString()); - } catch (InterruptedException ex) { - pw.println(); - pw.println("Failed to dump thread state due to interrupted exception!"); + mHandler.runWithScissors(new Runnable() { + @Override + public void run() { + dumpLocal(pw); } - } + }); } private void dumpLocal(PrintWriter pw) { diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java index c1047a98d033..0d9db900c393 100755 --- a/services/java/com/android/server/wm/WindowManagerService.java +++ b/services/java/com/android/server/wm/WindowManagerService.java @@ -739,116 +739,38 @@ public class WindowManagerService extends IWindowManager.Stub // For example, when this flag is true, there will be no wallpaper service. final boolean mOnlyCore; - public static WindowManagerService main(Context context, - PowerManagerService pm, DisplayManagerService dm, - boolean haveInputMethods, boolean allowBootMsgs, - boolean onlyCore) { - WMThread thr = new WMThread(context, pm, dm, haveInputMethods, allowBootMsgs, onlyCore); - thr.start(); - - synchronized (thr) { - while (thr.mService == null) { - try { - thr.wait(); - } catch (InterruptedException e) { - } + public static WindowManagerService main(final Context context, + final PowerManagerService pm, final DisplayManagerService dm, + final Handler uiHandler, final Handler wmHandler, + final boolean haveInputMethods, final boolean showBootMsgs, + final boolean onlyCore) { + final WindowManagerService[] holder = new WindowManagerService[1]; + wmHandler.runWithScissors(new Runnable() { + @Override + public void run() { + holder[0] = new WindowManagerService(context, pm, dm, + uiHandler, haveInputMethods, showBootMsgs, onlyCore); } - return thr.mService; - } - } - - static class WMThread extends Thread { - WindowManagerService mService; - - private final Context mContext; - private final PowerManagerService mPM; - private final DisplayManagerService mDisplayManager; - private final boolean mHaveInputMethods; - private final boolean mAllowBootMessages; - private final boolean mOnlyCore; - - public WMThread(Context context, PowerManagerService pm, - DisplayManagerService dm, - boolean haveInputMethods, boolean allowBootMsgs, boolean onlyCore) { - super("WindowManager"); - mContext = context; - mPM = pm; - mDisplayManager = dm; - mHaveInputMethods = haveInputMethods; - mAllowBootMessages = allowBootMsgs; - mOnlyCore = onlyCore; - } - - @Override - public void run() { - Looper.prepare(); - //Looper.myLooper().setMessageLogging(new LogPrinter( - // android.util.Log.DEBUG, TAG, android.util.Log.LOG_ID_SYSTEM)); - WindowManagerService s = new WindowManagerService(mContext, mPM, mDisplayManager, - mHaveInputMethods, mAllowBootMessages, mOnlyCore); - android.os.Process.setThreadPriority( - android.os.Process.THREAD_PRIORITY_DISPLAY); - android.os.Process.setCanSelfBackground(false); - - synchronized (this) { - mService = s; - notifyAll(); - } - - // For debug builds, log event loop stalls to dropbox for analysis. - if (StrictMode.conditionallyEnableDebugLogging()) { - Slog.i(TAG, "Enabled StrictMode logging for WMThread's Looper"); - } - - Looper.loop(); - } + }); + return holder[0]; } - static class PolicyThread extends Thread { - private final WindowManagerPolicy mPolicy; - private final WindowManagerService mService; - private final Context mContext; - boolean mRunning = false; + private void initPolicy(Handler uiHandler) { + uiHandler.runWithScissors(new Runnable() { + @Override + public void run() { + WindowManagerPolicyThread.set(Thread.currentThread(), Looper.myLooper()); - public PolicyThread(WindowManagerPolicy policy, - WindowManagerService service, Context context) { - super("WindowManagerPolicy"); - mPolicy = policy; - mService = service; - mContext = context; - } - - @Override - public void run() { - Looper.prepare(); - WindowManagerPolicyThread.set(this, Looper.myLooper()); - - //Looper.myLooper().setMessageLogging(new LogPrinter( - // Log.VERBOSE, "WindowManagerPolicy", Log.LOG_ID_SYSTEM)); - android.os.Process.setThreadPriority( - android.os.Process.THREAD_PRIORITY_FOREGROUND); - android.os.Process.setCanSelfBackground(false); - mPolicy.init(mContext, mService, mService); - mService.mAnimator.mAboveUniverseLayer = mPolicy.getAboveUniverseLayer() - * TYPE_LAYER_MULTIPLIER - + TYPE_LAYER_OFFSET; - - synchronized (this) { - mRunning = true; - notifyAll(); - } - - // For debug builds, log event loop stalls to dropbox for analysis. - if (StrictMode.conditionallyEnableDebugLogging()) { - Slog.i(TAG, "Enabled StrictMode for PolicyThread's Looper"); + mPolicy.init(mContext, WindowManagerService.this, WindowManagerService.this); + mAnimator.mAboveUniverseLayer = mPolicy.getAboveUniverseLayer() + * TYPE_LAYER_MULTIPLIER + + TYPE_LAYER_OFFSET; } - - Looper.loop(); - } + }); } private WindowManagerService(Context context, PowerManagerService pm, - DisplayManagerService displayManager, + DisplayManagerService displayManager, Handler uiHandler, boolean haveInputMethods, boolean showBootMsgs, boolean onlyCore) { mContext = context; mHaveInputMethods = haveInputMethods; @@ -857,7 +779,7 @@ public class WindowManagerService extends IWindowManager.Stub mLimitedAlphaCompositing = context.getResources().getBoolean( com.android.internal.R.bool.config_sf_limitedAlpha); mDisplayManagerService = displayManager; - mDisplayManager = DisplayManager.getInstance(); + mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE); mHeadless = displayManager.isHeadless(); mKeyguardDisableHandler = new KeyguardDisableHandler(mContext, mPolicy); @@ -895,17 +817,7 @@ public class WindowManagerService extends IWindowManager.Stub mFxSession = new SurfaceSession(); mAnimator = new WindowAnimator(this); - PolicyThread thr = new PolicyThread(mPolicy, this, context); - thr.start(); - - synchronized (thr) { - while (!thr.mRunning) { - try { - thr.wait(); - } catch (InterruptedException e) { - } - } - } + initPolicy(uiHandler); mInputManager.start(); @@ -6562,7 +6474,8 @@ public class WindowManagerService extends IWindowManager.Stub displayInfo.appHeight = appHeight; displayInfo.getLogicalMetrics(mRealDisplayMetrics, null); displayInfo.getAppMetrics(mDisplayMetrics, null); - mDisplayManagerService.setDisplayInfo(displayContent.getDisplayId(), displayInfo); + mDisplayManagerService.setDisplayInfoOverrideFromWindowManager( + displayContent.getDisplayId(), displayInfo); mAnimator.setDisplayDimensions(dw, dh, appWidth, appHeight); } @@ -6914,7 +6827,10 @@ public class WindowManagerService extends IWindowManager.Stub synchronized(displayContent.mDisplaySizeLock) { // Bootstrap the default logical display from the display manager. displayInfo = displayContent.getDisplayInfo(); - mDisplayManagerService.getDisplayInfo(displayId, displayInfo); + DisplayInfo newDisplayInfo = mDisplayManagerService.getDisplayInfo(displayId); + if (newDisplayInfo != null) { + displayInfo.copyFrom(newDisplayInfo); + } displayContent.mInitialDisplayWidth = displayInfo.logicalWidth; displayContent.mInitialDisplayHeight = displayInfo.logicalHeight; displayContent.mInitialDisplayDensity = displayInfo.logicalDensityDpi; @@ -10365,7 +10281,7 @@ public class WindowManagerService extends IWindowManager.Stub public DisplayContent getDisplayContent(final int displayId) { DisplayContent displayContent = mDisplayContents.get(displayId); if (displayContent == null) { - displayContent = new DisplayContent(mDisplayManager.getRealDisplay(displayId)); + displayContent = new DisplayContent(mDisplayManager.getDisplay(displayId)); mDisplayContents.put(displayId, displayContent); } return displayContent; -- cgit v1.2.3-59-g8ed1b