Commit 0b1a4bdf authored by mthiesse's avatar mthiesse Committed by Commit bot

Implement Virtual Display class for Android.

This CL defines a base class for DisplayAndroid, and separates out an implementation for an Android Display backed DisplayAndroidImpl, and a VirtualDisplayAndroid (currently intended for use by Chrome's VRShell and WebVR). Unlike the DisplayAndroidImpl, VirtualDisplayAndroid is mutable to display clients.

The display ID for a DisplayAndroid can no longer be assumed to match that of an Android Display.

BUG=643480

Review-Url: https://codereview.chromium.org/2523273002
Cr-Commit-Position: refs/heads/master@{#434806}
parent be556b76
......@@ -175,6 +175,8 @@ android_library("ui_java") {
"java/src/org/chromium/ui/display/DisplayAndroid.java",
"java/src/org/chromium/ui/display/DisplayAndroidManager.java",
"java/src/org/chromium/ui/display/DisplaySwitches.java",
"java/src/org/chromium/ui/display/PhysicalDisplayAndroid.java",
"java/src/org/chromium/ui/display/VirtualDisplayAndroid.java",
"java/src/org/chromium/ui/gfx/BitmapHelper.java",
"java/src/org/chromium/ui/gfx/DeviceDisplayInfo.java",
"java/src/org/chromium/ui/gfx/ViewConfigurationHelper.java",
......
......@@ -537,7 +537,7 @@ public class WindowAndroid {
*/
public long getNativePointer() {
if (mNativeWindowAndroid == 0) {
mNativeWindowAndroid = nativeInit(mDisplayAndroid.getSdkDisplayId());
mNativeWindowAndroid = nativeInit(mDisplayAndroid.getDisplayId());
}
return mNativeWindowAndroid;
}
......
......@@ -4,18 +4,11 @@
package org.chromium.ui.display;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.os.Build;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.Surface;
import org.chromium.base.CommandLine;
import org.chromium.base.Log;
import java.util.WeakHashMap;
/**
......@@ -45,55 +38,21 @@ public class DisplayAndroid {
void onDIPScaleChanged(float dipScale);
}
private static final String TAG = "DisplayAndroid";
private static final DisplayAndroidObserver[] EMPTY_OBSERVER_ARRAY =
new DisplayAndroidObserver[0];
private final int mSdkDisplayId;
private final WeakHashMap<DisplayAndroidObserver, Object /* null */> mObservers;
// Do NOT add strong references to objects with potentially complex lifetime, like Context.
// Updated by updateFromDisplay.
private final Point mSize;
private final Point mPhysicalSize;
private final DisplayMetrics mDisplayMetrics;
private final PixelFormat mPixelFormatInfo;
private int mPixelFormatId;
private final int mDisplayId;
private Point mSize;
private Point mPhysicalSize;
private float mDipScale;
private int mBitsPerPixel;
private int mBitsPerComponent;
private int mRotation;
// When this object exists, a positive value means that the forced DIP scale is set and
// the zero means it is not. The non existing object (i.e. null reference) means that
// the existence and value of the forced DIP scale has not yet been determined.
private static Float sForcedDIPScale;
private static boolean hasForcedDIPScale() {
if (sForcedDIPScale == null) {
String forcedScaleAsString = CommandLine.getInstance().getSwitchValue(
DisplaySwitches.FORCE_DEVICE_SCALE_FACTOR);
if (forcedScaleAsString == null) {
sForcedDIPScale = Float.valueOf(0.0f);
} else {
boolean isInvalid = false;
try {
sForcedDIPScale = Float.valueOf(forcedScaleAsString);
// Negative values are discarded.
if (sForcedDIPScale.floatValue() <= 0.0f) isInvalid = true;
} catch (NumberFormatException e) {
// Strings that do not represent numbers are discarded.
isInvalid = true;
}
if (isInvalid) {
Log.w(TAG, "Ignoring invalid forced DIP scale '" + forcedScaleAsString + "'");
sForcedDIPScale = Float.valueOf(0.0f);
}
}
}
return sForcedDIPScale.floatValue() > 0;
}
private static DisplayAndroidManager getManager() {
protected static DisplayAndroidManager getManager() {
return DisplayAndroidManager.getInstance();
}
......@@ -118,10 +77,10 @@ public class DisplayAndroid {
}
/**
* @return Display id as defined in Android's Display.
* @return Display id that does not necessarily match the one defined in Android's Display.
*/
public int getSdkDisplayId() {
return mSdkDisplayId;
public int getDisplayId() {
return mDisplayId;
}
/**
......@@ -163,7 +122,7 @@ public class DisplayAndroid {
* @return current orientation in degrees. One of the values 0, 90, 180, 270.
*/
/* package */ int getRotationDegrees() {
switch (mRotation) {
switch (getRotation()) {
case Surface.ROTATION_0:
return 0;
case Surface.ROTATION_90:
......@@ -183,14 +142,14 @@ public class DisplayAndroid {
* @return A scaling factor for the Density Independent Pixel unit.
*/
public float getDipScale() {
return mDisplayMetrics.density;
return mDipScale;
}
/**
* @return Number of bits per pixel.
*/
/* package */ int getBitsPerPixel() {
return mPixelFormatInfo.bitsPerPixel;
return mBitsPerPixel;
}
/**
......@@ -198,34 +157,7 @@ public class DisplayAndroid {
*/
@SuppressWarnings("deprecation")
/* package */ int getBitsPerComponent() {
switch (mPixelFormatId) {
case PixelFormat.RGBA_4444:
return 4;
case PixelFormat.RGBA_5551:
return 5;
case PixelFormat.RGBA_8888:
case PixelFormat.RGBX_8888:
case PixelFormat.RGB_888:
return 8;
case PixelFormat.RGB_332:
return 2;
case PixelFormat.RGB_565:
return 5;
// Non-RGB formats.
case PixelFormat.A_8:
case PixelFormat.LA_88:
case PixelFormat.L_8:
return 0;
// Unknown format. Use 8 as a sensible default.
default:
return 8;
}
return mBitsPerComponent;
}
/**
......@@ -259,70 +191,56 @@ public class DisplayAndroid {
getManager().startAccurateListening();
}
/* package */ DisplayAndroid(Display display) {
mSdkDisplayId = display.getDisplayId();
protected DisplayAndroid(int displayId) {
mDisplayId = displayId;
mObservers = new WeakHashMap<>();
mSize = new Point();
mPhysicalSize = new Point();
mDisplayMetrics = new DisplayMetrics();
mPixelFormatInfo = new PixelFormat();
}
@SuppressWarnings("deprecation")
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
/* package */ void updateFromDisplay(Display display) {
final Point oldSize = new Point(mSize);
final Point oldPhysicalSize = new Point(mPhysicalSize);
final float oldDensity = mDisplayMetrics.density;
final int oldPixelFormatId = mPixelFormatId;
final int oldRotation = mRotation;
display.getSize(mSize);
display.getMetrics(mDisplayMetrics);
if (hasForcedDIPScale()) mDisplayMetrics.density = sForcedDIPScale.floatValue();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
display.getRealSize(mPhysicalSize);
}
private DisplayAndroidObserver[] getObservers() {
// Makes a copy to allow concurrent edit.
return mObservers.keySet().toArray(EMPTY_OBSERVER_ARRAY);
}
// JellyBean MR1 and later always uses RGBA_8888.
mPixelFormatId = (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1)
? display.getPixelFormat()
: PixelFormat.RGBA_8888;
if (oldPixelFormatId != mPixelFormatId) {
PixelFormat.getPixelFormatInfo(mPixelFormatId, mPixelFormatInfo);
}
/**
* Update the display to the provided parameters. Null values leave the parameter unchanged.
*/
protected void update(Point size, Point physicalSize, Float dipScale, Integer bitsPerPixel,
Integer bitsPerComponent, Integer rotation) {
boolean sizeChanged = size != null && !mSize.equals(size);
boolean physicalSizeChanged = physicalSize != null && !mPhysicalSize.equals(physicalSize);
// Intentional comparison of floats: we assume that if scales differ, they differ
// significantly.
boolean dipScaleChanged = dipScale != null && mDipScale != dipScale;
boolean bitsPerPixelChanged = bitsPerPixel != null && mBitsPerPixel != bitsPerPixel;
boolean bitsPerComponentChanged = bitsPerComponent != null
&& mBitsPerComponent != bitsPerComponent;
boolean rotationChanged = rotation != null && mRotation != rotation;
mRotation = display.getRotation();
boolean changed = sizeChanged || physicalSizeChanged || dipScaleChanged
|| bitsPerPixelChanged || bitsPerComponentChanged || rotationChanged;
if (!changed) return;
final boolean noChanges = oldSize.equals(mSize) && oldPhysicalSize.equals(mPhysicalSize)
&& oldDensity == mDisplayMetrics.density && oldPixelFormatId == mPixelFormatId
&& oldRotation == mRotation;
if (noChanges) return;
if (sizeChanged) mSize = size;
if (physicalSizeChanged) mPhysicalSize = physicalSize;
if (dipScaleChanged) mDipScale = dipScale;
if (bitsPerPixelChanged) mBitsPerPixel = bitsPerPixel;
if (bitsPerComponentChanged) mBitsPerComponent = bitsPerComponent;
if (rotationChanged) mRotation = rotation;
getManager().updateDisplayOnNativeSide(this);
if (oldRotation != mRotation) {
if (rotationChanged) {
DisplayAndroidObserver[] observers = getObservers();
for (DisplayAndroidObserver o : observers) {
o.onRotationChanged(mRotation);
}
}
// Intentional comparison of floats: we assume that if scales differ,
// they differ significantly.
boolean dipScaleChanged = oldDensity != mDisplayMetrics.density;
if (dipScaleChanged) {
DisplayAndroidObserver[] observers = getObservers();
for (DisplayAndroidObserver o : observers) {
o.onDIPScaleChanged(mDisplayMetrics.density);
o.onDIPScaleChanged(mDipScale);
}
}
}
private DisplayAndroidObserver[] getObservers() {
// Makes a copy to allow concurrent edit.
return mObservers.keySet().toArray(EMPTY_OBSERVER_ARRAY);
}
}
......@@ -112,7 +112,7 @@ import org.chromium.ui.gfx.DeviceDisplayInfo;
@Override
public void onConfigurationChanged(Configuration newConfig) {
updateDeviceDisplayInfo();
mIdMap.get(mMainSdkDisplayId).updateFromDisplay(
((PhysicalDisplayAndroid) mIdMap.get(mMainSdkDisplayId)).updateFromDisplay(
getDefaultDisplayForContext(getContext()));
}
......@@ -171,7 +171,8 @@ import org.chromium.ui.gfx.DeviceDisplayInfo;
@Override
public void onDisplayChanged(int sdkDisplayId) {
updateDeviceDisplayInfo();
DisplayAndroid displayAndroid = mIdMap.get(sdkDisplayId);
PhysicalDisplayAndroid displayAndroid =
(PhysicalDisplayAndroid) mIdMap.get(sdkDisplayId);
if (displayAndroid != null) {
displayAndroid.updateFromDisplay(getDisplayManager().getDisplay(sdkDisplayId));
}
......@@ -180,10 +181,17 @@ import org.chromium.ui.gfx.DeviceDisplayInfo;
private static DisplayAndroidManager sDisplayAndroidManager;
// Real displays (as in, displays backed by an Android Display and recognized by the OS, though
// not necessarily physical displays) on Android start at ID 0, and increment indefinitely as
// displays are added. Display IDs are never reused until reboot. To avoid any overlap, start
// virtual display ids at a much higher number, and increment them in the same way.
private static final int VIRTUAL_DISPLAY_ID_BEGIN = Integer.MAX_VALUE / 2;
private long mNativePointer;
private int mMainSdkDisplayId;
private SparseArray<DisplayAndroid> mIdMap;
private DisplayListenerBackend mBackend;
private int mNextVirtualDisplayId = VIRTUAL_DISPLAY_ID_BEGIN;
@SuppressFBWarnings("LI_LAZY_INIT_UPDATE_STATIC")
/* package */ static DisplayAndroidManager getInstance() {
......@@ -278,16 +286,36 @@ import org.chromium.ui.gfx.DeviceDisplayInfo;
private DisplayAndroid addDisplay(Display display) {
int sdkDisplayId = display.getDisplayId();
DisplayAndroid displayAndroid = new DisplayAndroid(display);
PhysicalDisplayAndroid displayAndroid = new PhysicalDisplayAndroid(display);
assert mIdMap.get(sdkDisplayId) == null;
mIdMap.put(sdkDisplayId, displayAndroid);
displayAndroid.updateFromDisplay(display);
return displayAndroid;
}
private int getNextVirtualDisplayId() {
return mNextVirtualDisplayId++;
}
/* package */ VirtualDisplayAndroid addVirtualDisplay() {
VirtualDisplayAndroid display = new VirtualDisplayAndroid(getNextVirtualDisplayId());
assert mIdMap.get(display.getDisplayId()) == null;
mIdMap.put(display.getDisplayId(), display);
updateDisplayOnNativeSide(display);
return display;
}
/* package */ void removeVirtualDisplay(VirtualDisplayAndroid display) {
DisplayAndroid displayAndroid = mIdMap.get(display.getDisplayId());
assert displayAndroid == display;
if (mNativePointer != 0) nativeRemoveDisplay(mNativePointer, display.getDisplayId());
mIdMap.remove(display.getDisplayId());
}
/* package */ void updateDisplayOnNativeSide(DisplayAndroid displayAndroid) {
if (mNativePointer == 0) return;
nativeUpdateDisplay(mNativePointer, displayAndroid.getSdkDisplayId(),
nativeUpdateDisplay(mNativePointer, displayAndroid.getDisplayId(),
displayAndroid.getPhysicalDisplayWidth(), displayAndroid.getPhysicalDisplayHeight(),
displayAndroid.getDisplayWidth(), displayAndroid.getDisplayHeight(),
displayAndroid.getDipScale(), displayAndroid.getRotationDegrees(),
......
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.ui.display;
import android.annotation.TargetApi;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.os.Build;
import android.util.DisplayMetrics;
import android.view.Display;
import org.chromium.base.CommandLine;
import org.chromium.base.Log;
/**
* A DisplayAndroid implementation tied to a physical Display.
*/
/* package */ class PhysicalDisplayAndroid extends DisplayAndroid {
private static final String TAG = "DisplayAndroid";
// When this object exists, a positive value means that the forced DIP scale is set and
// the zero means it is not. The non existing object (i.e. null reference) means that
// the existence and value of the forced DIP scale has not yet been determined.
private static Float sForcedDIPScale;
private static boolean hasForcedDIPScale() {
if (sForcedDIPScale == null) {
String forcedScaleAsString = CommandLine.getInstance().getSwitchValue(
DisplaySwitches.FORCE_DEVICE_SCALE_FACTOR);
if (forcedScaleAsString == null) {
sForcedDIPScale = Float.valueOf(0.0f);
} else {
boolean isInvalid = false;
try {
sForcedDIPScale = Float.valueOf(forcedScaleAsString);
// Negative values are discarded.
if (sForcedDIPScale.floatValue() <= 0.0f) isInvalid = true;
} catch (NumberFormatException e) {
// Strings that do not represent numbers are discarded.
isInvalid = true;
}
if (isInvalid) {
Log.w(TAG, "Ignoring invalid forced DIP scale '" + forcedScaleAsString + "'");
sForcedDIPScale = Float.valueOf(0.0f);
}
}
}
return sForcedDIPScale.floatValue() > 0;
}
@SuppressWarnings("deprecation")
private int bitsPerComponent(int pixelFormatId) {
switch (pixelFormatId) {
case PixelFormat.RGBA_4444:
return 4;
case PixelFormat.RGBA_5551:
return 5;
case PixelFormat.RGBA_8888:
case PixelFormat.RGBX_8888:
case PixelFormat.RGB_888:
return 8;
case PixelFormat.RGB_332:
return 2;
case PixelFormat.RGB_565:
return 5;
// Non-RGB formats.
case PixelFormat.A_8:
case PixelFormat.LA_88:
case PixelFormat.L_8:
return 0;
// Unknown format. Use 8 as a sensible default.
default:
return 8;
}
}
/* package */ PhysicalDisplayAndroid(Display display) {
super(display.getDisplayId());
}
@SuppressWarnings("deprecation")
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
/* package */ void updateFromDisplay(Display display) {
Point size = new Point();
Point physicalSize = new Point();
DisplayMetrics displayMetrics = new DisplayMetrics();
PixelFormat pixelFormat = new PixelFormat();
display.getSize(size);
display.getMetrics(displayMetrics);
if (hasForcedDIPScale()) displayMetrics.density = sForcedDIPScale.floatValue();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
display.getRealSize(physicalSize);
}
// JellyBean MR1 and later always uses RGBA_8888.
int pixelFormatId = (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1)
? display.getPixelFormat()
: PixelFormat.RGBA_8888;
PixelFormat.getPixelFormatInfo(pixelFormatId, pixelFormat);
super.update(size, physicalSize, displayMetrics.density, pixelFormat.bitsPerPixel,
bitsPerComponent(pixelFormatId), display.getRotation());
}
}
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.ui.display;
import android.graphics.Point;
/**
* An instance of DisplayAndroid not associated with any physical display.
*/
public class VirtualDisplayAndroid extends DisplayAndroid {
public static VirtualDisplayAndroid createVirtualDisplay() {
return getManager().addVirtualDisplay();
}
/* package */ VirtualDisplayAndroid(int displayId) {
super(displayId);
}
/**
* @param other Sets the properties of this display to those of the other display.
*/
public void setTo(DisplayAndroid other) {
update(new Point(other.getDisplayWidth(), other.getDisplayHeight()),
new Point(other.getPhysicalDisplayWidth(), other.getPhysicalDisplayHeight()),
other.getDipScale(), other.getBitsPerPixel(),
other.getBitsPerComponent(), other.getRotation());
}
@Override
public void update(Point size, Point physicalSize, Float dipScale, Integer bitsPerPixel,
Integer bitsPerComponent, Integer rotation) {
super.update(size, physicalSize, dipScale, bitsPerPixel, bitsPerComponent, rotation);
}
/**
* Removes this Virtual Display from the DisplayManger.
*/
public void destroy() {
getManager().removeVirtualDisplay(this);
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment