Home | History | Annotate | Download | only in display
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.server.display;
     18 
     19 import com.android.internal.util.DumpUtils;
     20 import com.android.internal.util.IndentingPrintWriter;
     21 
     22 import android.content.Context;
     23 import android.database.ContentObserver;
     24 import android.graphics.SurfaceTexture;
     25 import android.os.Handler;
     26 import android.os.IBinder;
     27 import android.provider.Settings;
     28 import android.util.DisplayMetrics;
     29 import android.util.Slog;
     30 import android.view.Display;
     31 import android.view.Gravity;
     32 import android.view.Surface;
     33 import android.view.SurfaceControl;
     34 
     35 import java.io.PrintWriter;
     36 import java.util.ArrayList;
     37 import java.util.Arrays;
     38 import java.util.List;
     39 import java.util.regex.Matcher;
     40 import java.util.regex.Pattern;
     41 
     42 /**
     43  * A display adapter that uses overlay windows to simulate secondary displays
     44  * for development purposes.  Use Development Settings to enable one or more
     45  * overlay displays.
     46  * <p>
     47  * This object has two different handlers (which may be the same) which must not
     48  * get confused.  The main handler is used to posting messages to the display manager
     49  * service as usual.  The UI handler is only used by the {@link OverlayDisplayWindow}.
     50  * </p><p>
     51  * Display adapters are guarded by the {@link DisplayManagerService.SyncRoot} lock.
     52  * </p><p>
     53  * This adapter is configured via the
     54  * {@link android.provider.Settings.Global#OVERLAY_DISPLAY_DEVICES} setting. This setting should be
     55  * formatted as follows:
     56  * <pre>
     57  * [mode1]|[mode2]|...,[flag1],[flag2],...
     58  * </pre>
     59  * with each mode specified as:
     60  * <pre>
     61  * [width]x[height]/[densityDpi]
     62  * </pre>
     63  * Supported flags:
     64  * <ul>
     65  * <li><pre>secure</pre>: creates a secure display</li>
     66  * </ul>
     67  * </p>
     68  */
     69 final class OverlayDisplayAdapter extends DisplayAdapter {
     70     static final String TAG = "OverlayDisplayAdapter";
     71     static final boolean DEBUG = false;
     72 
     73     private static final int MIN_WIDTH = 100;
     74     private static final int MIN_HEIGHT = 100;
     75     private static final int MAX_WIDTH = 4096;
     76     private static final int MAX_HEIGHT = 4096;
     77 
     78     private static final Pattern DISPLAY_PATTERN =
     79             Pattern.compile("([^,]+)(,[a-z]+)*");
     80     private static final Pattern MODE_PATTERN =
     81             Pattern.compile("(\\d+)x(\\d+)/(\\d+)");
     82 
     83     // Unique id prefix for overlay displays.
     84     private static final String UNIQUE_ID_PREFIX = "overlay:";
     85 
     86     private final Handler mUiHandler;
     87     private final ArrayList<OverlayDisplayHandle> mOverlays =
     88             new ArrayList<OverlayDisplayHandle>();
     89     private String mCurrentOverlaySetting = "";
     90 
     91     // Called with SyncRoot lock held.
     92     public OverlayDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
     93             Context context, Handler handler, Listener listener, Handler uiHandler) {
     94         super(syncRoot, context, handler, listener, TAG);
     95         mUiHandler = uiHandler;
     96     }
     97 
     98     @Override
     99     public void dumpLocked(PrintWriter pw) {
    100         super.dumpLocked(pw);
    101 
    102         pw.println("mCurrentOverlaySetting=" + mCurrentOverlaySetting);
    103         pw.println("mOverlays: size=" + mOverlays.size());
    104         for (OverlayDisplayHandle overlay : mOverlays) {
    105             overlay.dumpLocked(pw);
    106         }
    107     }
    108 
    109     @Override
    110     public void registerLocked() {
    111         super.registerLocked();
    112 
    113         getHandler().post(new Runnable() {
    114             @Override
    115             public void run() {
    116                 getContext().getContentResolver().registerContentObserver(
    117                         Settings.Global.getUriFor(Settings.Global.OVERLAY_DISPLAY_DEVICES),
    118                         true, new ContentObserver(getHandler()) {
    119                             @Override
    120                             public void onChange(boolean selfChange) {
    121                                 updateOverlayDisplayDevices();
    122                             }
    123                         });
    124 
    125                 updateOverlayDisplayDevices();
    126             }
    127         });
    128     }
    129 
    130     private void updateOverlayDisplayDevices() {
    131         synchronized (getSyncRoot()) {
    132             updateOverlayDisplayDevicesLocked();
    133         }
    134     }
    135 
    136     private void updateOverlayDisplayDevicesLocked() {
    137         String value = Settings.Global.getString(getContext().getContentResolver(),
    138                 Settings.Global.OVERLAY_DISPLAY_DEVICES);
    139         if (value == null) {
    140             value = "";
    141         }
    142 
    143         if (value.equals(mCurrentOverlaySetting)) {
    144             return;
    145         }
    146         mCurrentOverlaySetting = value;
    147 
    148         if (!mOverlays.isEmpty()) {
    149             Slog.i(TAG, "Dismissing all overlay display devices.");
    150             for (OverlayDisplayHandle overlay : mOverlays) {
    151                 overlay.dismissLocked();
    152             }
    153             mOverlays.clear();
    154         }
    155 
    156         int count = 0;
    157         for (String part : value.split(";")) {
    158             Matcher displayMatcher = DISPLAY_PATTERN.matcher(part);
    159             if (displayMatcher.matches()) {
    160                 if (count >= 4) {
    161                     Slog.w(TAG, "Too many overlay display devices specified: " + value);
    162                     break;
    163                 }
    164                 String modeString = displayMatcher.group(1);
    165                 String flagString = displayMatcher.group(2);
    166                 ArrayList<OverlayMode> modes = new ArrayList<>();
    167                 for (String mode : modeString.split("\\|")) {
    168                     Matcher modeMatcher = MODE_PATTERN.matcher(mode);
    169                     if (modeMatcher.matches()) {
    170                         try {
    171                             int width = Integer.parseInt(modeMatcher.group(1), 10);
    172                             int height = Integer.parseInt(modeMatcher.group(2), 10);
    173                             int densityDpi = Integer.parseInt(modeMatcher.group(3), 10);
    174                             if (width >= MIN_WIDTH && width <= MAX_WIDTH
    175                                     && height >= MIN_HEIGHT && height <= MAX_HEIGHT
    176                                     && densityDpi >= DisplayMetrics.DENSITY_LOW
    177                                     && densityDpi <= DisplayMetrics.DENSITY_XXXHIGH) {
    178                                 modes.add(new OverlayMode(width, height, densityDpi));
    179                                 continue;
    180                             } else {
    181                                 Slog.w(TAG, "Ignoring out-of-range overlay display mode: " + mode);
    182                             }
    183                         } catch (NumberFormatException ex) {
    184                         }
    185                     } else if (mode.isEmpty()) {
    186                         continue;
    187                     }
    188                 }
    189                 if (!modes.isEmpty()) {
    190                     int number = ++count;
    191                     String name = getContext().getResources().getString(
    192                             com.android.internal.R.string.display_manager_overlay_display_name,
    193                             number);
    194                     int gravity = chooseOverlayGravity(number);
    195                     boolean secure = flagString != null && flagString.contains(",secure");
    196 
    197                     Slog.i(TAG, "Showing overlay display device #" + number
    198                             + ": name=" + name + ", modes=" + Arrays.toString(modes.toArray()));
    199 
    200                     mOverlays.add(new OverlayDisplayHandle(name, modes, gravity, secure, number));
    201                     continue;
    202                 }
    203             }
    204             Slog.w(TAG, "Malformed overlay display devices setting: " + value);
    205         }
    206     }
    207 
    208     private static int chooseOverlayGravity(int overlayNumber) {
    209         switch (overlayNumber) {
    210             case 1:
    211                 return Gravity.TOP | Gravity.LEFT;
    212             case 2:
    213                 return Gravity.BOTTOM | Gravity.RIGHT;
    214             case 3:
    215                 return Gravity.TOP | Gravity.RIGHT;
    216             case 4:
    217             default:
    218                 return Gravity.BOTTOM | Gravity.LEFT;
    219         }
    220     }
    221 
    222     private abstract class OverlayDisplayDevice extends DisplayDevice {
    223         private final String mName;
    224         private final float mRefreshRate;
    225         private final long mDisplayPresentationDeadlineNanos;
    226         private final boolean mSecure;
    227         private final List<OverlayMode> mRawModes;
    228         private final Display.Mode[] mModes;
    229         private final int mDefaultMode;
    230 
    231         private int mState;
    232         private SurfaceTexture mSurfaceTexture;
    233         private Surface mSurface;
    234         private DisplayDeviceInfo mInfo;
    235         private int mActiveMode;
    236 
    237         public OverlayDisplayDevice(IBinder displayToken, String name,
    238                 List<OverlayMode> modes, int activeMode, int defaultMode,
    239                 float refreshRate, long presentationDeadlineNanos,
    240                 boolean secure, int state,
    241                 SurfaceTexture surfaceTexture, int number) {
    242             super(OverlayDisplayAdapter.this, displayToken, UNIQUE_ID_PREFIX + number);
    243             mName = name;
    244             mRefreshRate = refreshRate;
    245             mDisplayPresentationDeadlineNanos = presentationDeadlineNanos;
    246             mSecure = secure;
    247             mState = state;
    248             mSurfaceTexture = surfaceTexture;
    249             mRawModes = modes;
    250             mModes = new Display.Mode[modes.size()];
    251             for (int i = 0; i < modes.size(); i++) {
    252                 OverlayMode mode = modes.get(i);
    253                 mModes[i] = createMode(mode.mWidth, mode.mHeight, refreshRate);
    254             }
    255             mActiveMode = activeMode;
    256             mDefaultMode = defaultMode;
    257         }
    258 
    259         public void destroyLocked() {
    260             mSurfaceTexture = null;
    261             if (mSurface != null) {
    262                 mSurface.release();
    263                 mSurface = null;
    264             }
    265             SurfaceControl.destroyDisplay(getDisplayTokenLocked());
    266         }
    267 
    268         @Override
    269         public boolean hasStableUniqueId() {
    270             return false;
    271         }
    272 
    273         @Override
    274         public void performTraversalInTransactionLocked() {
    275             if (mSurfaceTexture != null) {
    276                 if (mSurface == null) {
    277                     mSurface = new Surface(mSurfaceTexture);
    278                 }
    279                 setSurfaceInTransactionLocked(mSurface);
    280             }
    281         }
    282 
    283         public void setStateLocked(int state) {
    284             mState = state;
    285             mInfo = null;
    286         }
    287 
    288         @Override
    289         public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
    290             if (mInfo == null) {
    291                 Display.Mode mode = mModes[mActiveMode];
    292                 OverlayMode rawMode = mRawModes.get(mActiveMode);
    293                 mInfo = new DisplayDeviceInfo();
    294                 mInfo.name = mName;
    295                 mInfo.uniqueId = getUniqueId();
    296                 mInfo.width = mode.getPhysicalWidth();
    297                 mInfo.height = mode.getPhysicalHeight();
    298                 mInfo.modeId = mode.getModeId();
    299                 mInfo.defaultModeId = mModes[0].getModeId();
    300                 mInfo.supportedModes = mModes;
    301                 mInfo.densityDpi = rawMode.mDensityDpi;
    302                 mInfo.xDpi = rawMode.mDensityDpi;
    303                 mInfo.yDpi = rawMode.mDensityDpi;
    304                 mInfo.presentationDeadlineNanos = mDisplayPresentationDeadlineNanos +
    305                         1000000000L / (int) mRefreshRate;   // display's deadline + 1 frame
    306                 mInfo.flags = DisplayDeviceInfo.FLAG_PRESENTATION;
    307                 if (mSecure) {
    308                     mInfo.flags |= DisplayDeviceInfo.FLAG_SECURE;
    309                 }
    310                 mInfo.type = Display.TYPE_OVERLAY;
    311                 mInfo.touch = DisplayDeviceInfo.TOUCH_NONE;
    312                 mInfo.state = mState;
    313             }
    314             return mInfo;
    315         }
    316 
    317         @Override
    318         public void requestDisplayModesInTransactionLocked(int color, int id) {
    319             int index = -1;
    320             if (id == 0) {
    321                 // Use the default.
    322                 index = 0;
    323             } else {
    324                 for (int i = 0; i < mModes.length; i++) {
    325                     if (mModes[i].getModeId() == id) {
    326                         index = i;
    327                         break;
    328                     }
    329                 }
    330             }
    331             if (index == -1) {
    332                 Slog.w(TAG, "Unable to locate mode " + id + ", reverting to default.");
    333                 index = mDefaultMode;
    334             }
    335             if (mActiveMode == index) {
    336                 return;
    337             }
    338             mActiveMode = index;
    339             mInfo = null;
    340             sendDisplayDeviceEventLocked(this, DISPLAY_DEVICE_EVENT_CHANGED);
    341             onModeChangedLocked(index);
    342         }
    343 
    344         /**
    345          * Called when the device switched to a new mode.
    346          *
    347          * @param index index of the mode in the list of modes
    348          */
    349         public abstract void onModeChangedLocked(int index);
    350     }
    351 
    352     /**
    353      * Functions as a handle for overlay display devices which are created and
    354      * destroyed asynchronously.
    355      *
    356      * Guarded by the {@link DisplayManagerService.SyncRoot} lock.
    357      */
    358     private final class OverlayDisplayHandle implements OverlayDisplayWindow.Listener {
    359         private static final int DEFAULT_MODE_INDEX = 0;
    360 
    361         private final String mName;
    362         private final List<OverlayMode> mModes;
    363         private final int mGravity;
    364         private final boolean mSecure;
    365         private final int mNumber;
    366 
    367         private OverlayDisplayWindow mWindow;
    368         private OverlayDisplayDevice mDevice;
    369         private int mActiveMode;
    370 
    371         public OverlayDisplayHandle(String name, List<OverlayMode> modes, int gravity,
    372                 boolean secure, int number) {
    373             mName = name;
    374             mModes = modes;
    375             mGravity = gravity;
    376             mSecure = secure;
    377             mNumber = number;
    378 
    379             mActiveMode = 0;
    380 
    381             showLocked();
    382         }
    383 
    384         private void showLocked() {
    385             mUiHandler.post(mShowRunnable);
    386         }
    387 
    388         public void dismissLocked() {
    389             mUiHandler.removeCallbacks(mShowRunnable);
    390             mUiHandler.post(mDismissRunnable);
    391         }
    392 
    393         private void onActiveModeChangedLocked(int index) {
    394             mUiHandler.removeCallbacks(mResizeRunnable);
    395             mActiveMode = index;
    396             if (mWindow != null) {
    397                 mUiHandler.post(mResizeRunnable);
    398             }
    399         }
    400 
    401         // Called on the UI thread.
    402         @Override
    403         public void onWindowCreated(SurfaceTexture surfaceTexture, float refreshRate,
    404                 long presentationDeadlineNanos, int state) {
    405             synchronized (getSyncRoot()) {
    406                 IBinder displayToken = SurfaceControl.createDisplay(mName, mSecure);
    407                 mDevice = new OverlayDisplayDevice(displayToken, mName, mModes, mActiveMode,
    408                         DEFAULT_MODE_INDEX, refreshRate, presentationDeadlineNanos,
    409                         mSecure, state, surfaceTexture, mNumber) {
    410                     @Override
    411                     public void onModeChangedLocked(int index) {
    412                         onActiveModeChangedLocked(index);
    413                     }
    414                 };
    415 
    416                 sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_ADDED);
    417             }
    418         }
    419 
    420         // Called on the UI thread.
    421         @Override
    422         public void onWindowDestroyed() {
    423             synchronized (getSyncRoot()) {
    424                 if (mDevice != null) {
    425                     mDevice.destroyLocked();
    426                     sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_REMOVED);
    427                 }
    428             }
    429         }
    430 
    431         // Called on the UI thread.
    432         @Override
    433         public void onStateChanged(int state) {
    434             synchronized (getSyncRoot()) {
    435                 if (mDevice != null) {
    436                     mDevice.setStateLocked(state);
    437                     sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_CHANGED);
    438                 }
    439             }
    440         }
    441 
    442         public void dumpLocked(PrintWriter pw) {
    443             pw.println("  " + mName + ":");
    444             pw.println("    mModes=" + Arrays.toString(mModes.toArray()));
    445             pw.println("    mActiveMode=" + mActiveMode);
    446             pw.println("    mGravity=" + mGravity);
    447             pw.println("    mSecure=" + mSecure);
    448             pw.println("    mNumber=" + mNumber);
    449 
    450             // Try to dump the window state.
    451             if (mWindow != null) {
    452                 final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "    ");
    453                 ipw.increaseIndent();
    454                 DumpUtils.dumpAsync(mUiHandler, mWindow, ipw, "", 200);
    455             }
    456         }
    457 
    458         // Runs on the UI thread.
    459         private final Runnable mShowRunnable = new Runnable() {
    460             @Override
    461             public void run() {
    462                 OverlayMode mode = mModes.get(mActiveMode);
    463                 OverlayDisplayWindow window = new OverlayDisplayWindow(getContext(),
    464                         mName, mode.mWidth, mode.mHeight, mode.mDensityDpi, mGravity, mSecure,
    465                         OverlayDisplayHandle.this);
    466                 window.show();
    467 
    468                 synchronized (getSyncRoot()) {
    469                     mWindow = window;
    470                 }
    471             }
    472         };
    473 
    474         // Runs on the UI thread.
    475         private final Runnable mDismissRunnable = new Runnable() {
    476             @Override
    477             public void run() {
    478                 OverlayDisplayWindow window;
    479                 synchronized (getSyncRoot()) {
    480                     window = mWindow;
    481                     mWindow = null;
    482                 }
    483 
    484                 if (window != null) {
    485                     window.dismiss();
    486                 }
    487             }
    488         };
    489 
    490         // Runs on the UI thread.
    491         private final Runnable mResizeRunnable = new Runnable() {
    492             @Override
    493             public void run() {
    494                 OverlayMode mode;
    495                 OverlayDisplayWindow window;
    496                 synchronized (getSyncRoot()) {
    497                     if (mWindow == null) {
    498                         return;
    499                     }
    500                     mode = mModes.get(mActiveMode);
    501                     window = mWindow;
    502                 }
    503                 window.resize(mode.mWidth, mode.mHeight, mode.mDensityDpi);
    504             }
    505         };
    506     }
    507 
    508     /**
    509      * A display mode for an overlay display.
    510      */
    511     private static final class OverlayMode {
    512         final int mWidth;
    513         final int mHeight;
    514         final int mDensityDpi;
    515 
    516         OverlayMode(int width, int height, int densityDpi) {
    517             mWidth = width;
    518             mHeight = height;
    519             mDensityDpi = densityDpi;
    520         }
    521 
    522         @Override
    523         public String toString() {
    524             return new StringBuilder("{")
    525                     .append("width=").append(mWidth)
    526                     .append(", height=").append(mHeight)
    527                     .append(", densityDpi=").append(mDensityDpi)
    528                     .append("}")
    529                     .toString();
    530         }
    531     }
    532 }
    533