diff options
Diffstat (limited to 'core')
-rw-r--r-- | core/java/android/app/ActivityThread.java | 4 | ||||
-rw-r--r-- | core/java/android/app/ApplicationThreadNative.java | 7 | ||||
-rw-r--r-- | core/java/android/app/IApplicationThread.java | 3 | ||||
-rw-r--r-- | core/java/android/hardware/Sensor.java | 40 | ||||
-rw-r--r-- | core/java/android/net/PacProxySelector.java | 80 | ||||
-rw-r--r-- | core/java/android/net/Proxy.java | 48 | ||||
-rw-r--r-- | core/java/android/net/ProxyProperties.java | 45 | ||||
-rw-r--r-- | core/java/android/provider/Settings.java | 15 | ||||
-rw-r--r-- | core/java/android/view/inputmethod/InputMethodManager.java | 3 | ||||
-rw-r--r-- | core/java/android/widget/AbsListView.java | 1 | ||||
-rw-r--r-- | core/java/android/widget/FastScroller.java | 105 | ||||
-rw-r--r-- | core/java/com/android/internal/view/IInputMethodManager.aidl | 4 | ||||
-rw-r--r-- | core/tests/coretests/AndroidManifest.xml | 6 | ||||
-rw-r--r-- | core/tests/coretests/src/android/hardware/display/VirtualDisplayTest.java | 508 |
14 files changed, 799 insertions, 70 deletions
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index d9f9d61..5300eca 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -793,8 +793,8 @@ public final class ActivityThread { InetAddress.clearDnsCache(); } - public void setHttpProxy(String host, String port, String exclList) { - Proxy.setHttpProxySystemProperty(host, port, exclList); + public void setHttpProxy(String host, String port, String exclList, String pacFileUrl) { + Proxy.setHttpProxySystemProperty(host, port, exclList, pacFileUrl); } public void processInBackground() { diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java index 6f18e84..1465de2 100644 --- a/core/java/android/app/ApplicationThreadNative.java +++ b/core/java/android/app/ApplicationThreadNative.java @@ -338,7 +338,8 @@ public abstract class ApplicationThreadNative extends Binder final String proxy = data.readString(); final String port = data.readString(); final String exclList = data.readString(); - setHttpProxy(proxy, port, exclList); + final String pacFileUrl = data.readString(); + setHttpProxy(proxy, port, exclList, pacFileUrl); return true; } @@ -1001,12 +1002,14 @@ class ApplicationThreadProxy implements IApplicationThread { data.recycle(); } - public void setHttpProxy(String proxy, String port, String exclList) throws RemoteException { + public void setHttpProxy(String proxy, String port, String exclList, + String pacFileUrl) throws RemoteException { Parcel data = Parcel.obtain(); data.writeInterfaceToken(IApplicationThread.descriptor); data.writeString(proxy); data.writeString(port); data.writeString(exclList); + data.writeString(pacFileUrl); mRemote.transact(SET_HTTP_PROXY_TRANSACTION, data, null, IBinder.FLAG_ONEWAY); data.recycle(); } diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java index 8e882df..61c499a 100644 --- a/core/java/android/app/IApplicationThread.java +++ b/core/java/android/app/IApplicationThread.java @@ -101,7 +101,8 @@ public interface IApplicationThread extends IInterface { void scheduleConfigurationChanged(Configuration config) throws RemoteException; void updateTimeZone() throws RemoteException; void clearDnsCache() throws RemoteException; - void setHttpProxy(String proxy, String port, String exclList) throws RemoteException; + void setHttpProxy(String proxy, String port, String exclList, + String pacFileUrl) throws RemoteException; void processInBackground() throws RemoteException; void dumpService(FileDescriptor fd, IBinder servicetoken, String[] args) throws RemoteException; diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java index c3e9cb7..d708791 100644 --- a/core/java/android/hardware/Sensor.java +++ b/core/java/android/hardware/Sensor.java @@ -129,7 +129,7 @@ public final class Sensor { * due to distortions that arise from magnetized iron, steel or permanent magnets on the * device) is not considered in the given sensor values. However, such hard iron bias values * are returned to you separately in the result {@link android.hardware.SensorEvent#values} - * so you may use them for custom calibrations. + * so you may use them for custom calibrations. * <p>Also, no periodic calibration is performed * (i.e. there are no discontinuities in the data stream while using this sensor) and * assumptions that the magnetic field is due to the Earth's poles is avoided, but @@ -174,7 +174,7 @@ public final class Sensor { public static final int TYPE_GYROSCOPE_UNCALIBRATED = 16; /** - * A constant describing the significant motion trigger sensor. + * A constant describing a significant motion trigger sensor. * <p> * It triggers when an event occurs and then automatically disables * itself. The sensor continues to operate while the device is asleep @@ -186,6 +186,42 @@ public final class Sensor { public static final int TYPE_SIGNIFICANT_MOTION = 17; /** + * A constant describing a step detector sensor. + * <p> + * A sensor of this type triggers an event each time a step is taken by the user. The only + * allowed value to return is 1.0 and an event is generated for each step. Like with any other + * event, the timestamp indicates when the event (here the step) occurred, this corresponds to + * when the foot hit the ground, generating a high variation in acceleration. + * <p> + * See {@link android.hardware.SensorEvent#values SensorEvent.values} for more details. + */ + public static final int TYPE_STEP_DETECTOR = 18; + + /** + * A constant describing a step counter sensor. + * <p> + * A sensor of this type returns the number of steps taken by the user since the last reboot + * while activated. The value is returned as a float (with the fractional part set to zero) and + * is reset to zero only on a system reboot. The timestamp of the event is set to the time when + * the first step for that event was taken. This sensor is implemented in hardware and is + * expected to be low power. + * <p> + * See {@link android.hardware.SensorEvent#values SensorEvent.values} for more details. + */ + public static final int TYPE_STEP_COUNTER = 19; + + /** + * A constant describing the geo-magnetic rotation vector. + * <p> + * Similar to {@link #SENSOR_TYPE_ROTATION_VECTOR}, but using a magnetometer instead of using a + * gyroscope. This sensor uses lower power than the other rotation vectors, because it doesn't + * use the gyroscope. However, it is more noisy and will work best outdoors. + * <p> + * See {@link android.hardware.SensorEvent#values SensorEvent.values} for more details. + */ + public static final int TYPE_GEOMAGNETIC_ROTATION_VECTOR = 20; + + /** * A constant describing all sensor types. */ public static final int TYPE_ALL = -1; diff --git a/core/java/android/net/PacProxySelector.java b/core/java/android/net/PacProxySelector.java new file mode 100644 index 0000000..be3a31d --- /dev/null +++ b/core/java/android/net/PacProxySelector.java @@ -0,0 +1,80 @@ + +package android.net; + +import android.os.RemoteException; +import android.os.ServiceManager; + +import com.android.net.IProxyService; +import com.google.android.collect.Lists; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.MalformedURLException; +import java.net.Proxy; +import java.net.Proxy.Type; +import java.net.ProxySelector; +import java.net.SocketAddress; +import java.net.URI; +import java.util.List; + +/** + * @hide + */ +public class PacProxySelector extends ProxySelector { + public static final String PROXY_SERVICE = "com.android.net.IProxyService"; + private IProxyService mProxyService; + + public PacProxySelector() { + mProxyService = IProxyService.Stub.asInterface( + ServiceManager.getService(PROXY_SERVICE)); + } + + @Override + public List<Proxy> select(URI uri) { + String response = null; + String urlString; + try { + urlString = uri.toURL().toString(); + } catch (MalformedURLException e) { + urlString = uri.getHost(); + } + try { + response = mProxyService.resolvePacFile(uri.getHost(), urlString); + } catch (RemoteException e) { + e.printStackTrace(); + } + + return parseResponse(response); + } + + private static List<Proxy> parseResponse(String response) { + String[] split = response.split(";"); + List<Proxy> ret = Lists.newArrayList(); + for (String s : split) { + String trimmed = s.trim(); + if (trimmed.equals("DIRECT")) { + ret.add(java.net.Proxy.NO_PROXY); + } else if (trimmed.startsWith("PROXY ")) { + String[] hostPort = trimmed.substring(6).split(":"); + String host = hostPort[0]; + int port; + try { + port = Integer.parseInt(hostPort[1]); + } catch (Exception e) { + port = 8080; + } + ret.add(new Proxy(Type.HTTP, new InetSocketAddress(host, port))); + } + } + if (ret.size() == 0) { + ret.add(java.net.Proxy.NO_PROXY); + } + return ret; + } + + @Override + public void connectFailed(URI uri, SocketAddress address, IOException failure) { + + } + +} diff --git a/core/java/android/net/Proxy.java b/core/java/android/net/Proxy.java index a408ea0..5b38f57 100644 --- a/core/java/android/net/Proxy.java +++ b/core/java/android/net/Proxy.java @@ -18,38 +18,25 @@ package android.net; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; -import android.content.ContentResolver; import android.content.Context; -import android.database.ContentObserver; -import android.net.ProxyProperties; -import android.os.Handler; -import android.os.SystemProperties; import android.text.TextUtils; -import android.provider.Settings; import android.util.Log; -import java.net.InetAddress; + +import org.apache.http.HttpHost; +import org.apache.http.HttpRequest; +import org.apache.http.conn.routing.HttpRoute; +import org.apache.http.conn.routing.HttpRoutePlanner; +import org.apache.http.conn.scheme.SchemeRegistry; +import org.apache.http.protocol.HttpContext; + import java.net.InetSocketAddress; import java.net.ProxySelector; -import java.net.SocketAddress; import java.net.URI; -import java.net.UnknownHostException; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; -import junit.framework.Assert; - -import org.apache.http.conn.routing.HttpRoute; -import org.apache.http.conn.routing.HttpRoutePlanner; -import org.apache.http.conn.scheme.SchemeRegistry; -import org.apache.http.HttpHost; -import org.apache.http.HttpRequest; -import org.apache.http.impl.conn.ProxySelectorRoutePlanner; -import org.apache.http.protocol.HttpContext; - /** * A convenience class for accessing the user and default proxy * settings. @@ -60,6 +47,9 @@ public final class Proxy { private static final boolean DEBUG = false; private static final String TAG = "Proxy"; + private static final ProxySelector sDefaultProxySelector; + private static PacProxySelector sPacProxySelector; + /** * Used to notify an app that's caching the default connection proxy * that either the default connection or its proxy has changed. @@ -96,6 +86,7 @@ public final class Proxy { static { HOSTNAME_PATTERN = Pattern.compile(HOSTNAME_REGEXP); EXCLLIST_PATTERN = Pattern.compile(EXCLLIST_REGEXP); + sDefaultProxySelector = ProxySelector.getDefault(); } /** @@ -325,16 +316,19 @@ public final class Proxy { String host = null; String port = null; String exclList = null; + String pacFileUrl = null; if (p != null) { host = p.getHost(); port = Integer.toString(p.getPort()); exclList = p.getExclusionList(); + pacFileUrl = p.getPacFileUrl(); } - setHttpProxySystemProperty(host, port, exclList); + setHttpProxySystemProperty(host, port, exclList, pacFileUrl); } /** @hide */ - public static final void setHttpProxySystemProperty(String host, String port, String exclList) { + public static final void setHttpProxySystemProperty(String host, String port, String exclList, + String pacFileUrl) { if (exclList != null) exclList = exclList.replace(",", "|"); if (false) Log.d(TAG, "setHttpProxySystemProperty :"+host+":"+port+" - "+exclList); if (host != null) { @@ -358,5 +352,13 @@ public final class Proxy { System.clearProperty("http.nonProxyHosts"); System.clearProperty("https.nonProxyHosts"); } + if ((pacFileUrl != null) && !TextUtils.isEmpty(pacFileUrl)) { + if (sPacProxySelector == null) { + sPacProxySelector = new PacProxySelector(); + } + ProxySelector.setDefault(sPacProxySelector); + } else { + ProxySelector.setDefault(sDefaultProxySelector); + } } } diff --git a/core/java/android/net/ProxyProperties.java b/core/java/android/net/ProxyProperties.java index 9c4772b..76aea9f 100644 --- a/core/java/android/net/ProxyProperties.java +++ b/core/java/android/net/ProxyProperties.java @@ -20,9 +20,7 @@ package android.net; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; -import android.util.Log; -import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.util.Locale; @@ -38,17 +36,30 @@ public class ProxyProperties implements Parcelable { private String mExclusionList; private String[] mParsedExclusionList; + private String mPacFileUrl; + public static final String LOCAL_EXCL_LIST = ""; + public static final int LOCAL_PORT = 8182; + public static final String LOCAL_HOST = "localhost"; + public ProxyProperties(String host, int port, String exclList) { mHost = host; mPort = port; setExclusionList(exclList); } + public ProxyProperties(String pacFileUrl) { + mHost = LOCAL_HOST; + mPort = LOCAL_PORT; + setExclusionList(LOCAL_EXCL_LIST); + mPacFileUrl = pacFileUrl; + } + private ProxyProperties(String host, int port, String exclList, String[] parsedExclList) { mHost = host; mPort = port; mExclusionList = exclList; mParsedExclusionList = parsedExclList; + mPacFileUrl = null; } // copy constructor instead of clone @@ -56,6 +67,7 @@ public class ProxyProperties implements Parcelable { if (source != null) { mHost = source.getHost(); mPort = source.getPort(); + mPacFileUrl = source.getPacFileUrl(); mExclusionList = source.getExclusionList(); mParsedExclusionList = source.mParsedExclusionList; } @@ -69,6 +81,10 @@ public class ProxyProperties implements Parcelable { return inetSocketAddress; } + public String getPacFileUrl() { + return mPacFileUrl; + } + public String getHost() { return mHost; } @@ -130,7 +146,10 @@ public class ProxyProperties implements Parcelable { @Override public String toString() { StringBuilder sb = new StringBuilder(); - if (mHost != null) { + if (mPacFileUrl != null) { + sb.append("PAC Script: "); + sb.append(mPacFileUrl); + } else if (mHost != null) { sb.append("["); sb.append(mHost); sb.append("] "); @@ -148,6 +167,14 @@ public class ProxyProperties implements Parcelable { public boolean equals(Object o) { if (!(o instanceof ProxyProperties)) return false; ProxyProperties p = (ProxyProperties)o; + // If PAC URL is present in either then they must be equal. + // Other parameters will only be for fall back. + if (!TextUtils.isEmpty(mPacFileUrl)) { + return mPacFileUrl.equals(p.getPacFileUrl()); + } + if (!TextUtils.isEmpty(p.getPacFileUrl())) { + return false; + } if (mExclusionList != null && !mExclusionList.equals(p.getExclusionList())) return false; if (mHost != null && p.getHost() != null && mHost.equals(p.getHost()) == false) { return false; @@ -181,6 +208,13 @@ public class ProxyProperties implements Parcelable { * @hide */ public void writeToParcel(Parcel dest, int flags) { + if (mPacFileUrl != null) { + dest.writeByte((byte)1); + dest.writeString(mPacFileUrl); + return; + } else { + dest.writeByte((byte)0); + } if (mHost != null) { dest.writeByte((byte)1); dest.writeString(mHost); @@ -201,7 +235,10 @@ public class ProxyProperties implements Parcelable { public ProxyProperties createFromParcel(Parcel in) { String host = null; int port = 0; - if (in.readByte() == 1) { + if (in.readByte() != 0) { + return new ProxyProperties(in.readString()); + } + if (in.readByte() != 0) { host = in.readString(); port = in.readInt(); } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index b252641..130123f 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -5292,6 +5292,14 @@ public final class Settings { public static final String CONNECTIVITY_CHANGE_DELAY = "connectivity_change_delay"; /** + * The series of successively longer delays used in retrying to download PAC file. + * Last delay is used between successful PAC downloads. + * + * @hide + */ + public static final String PAC_CHANGE_DELAY = "pac_change_delay"; + + /** * Setting to turn off captive portal detection. Feature is enabled by * default and the setting needs to be set to 0 to disable it. * @@ -5386,6 +5394,13 @@ public final class Settings { GLOBAL_HTTP_PROXY_EXCLUSION_LIST = "global_http_proxy_exclusion_list"; /** + * The location PAC File for the proxy. + * @hide + */ + public static final String + GLOBAL_HTTP_PROXY_PAC = "global_proxy_pac_url"; + + /** * Enables the UI setting to allow the user to specify the global HTTP * proxy and associated exclusion list. * diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 4df4734..c8ce6fa 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -613,7 +613,8 @@ public final class InputMethodManager { public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(InputMethodInfo imi, boolean allowsImplicitlySelectedSubtypes) { try { - return mService.getEnabledInputMethodSubtypeList(imi, allowsImplicitlySelectedSubtypes); + return mService.getEnabledInputMethodSubtypeList( + imi == null ? null : imi.getId(), allowsImplicitlySelectedSubtypes); } catch (RemoteException e) { throw new RuntimeException(e); } diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index e7227e3..3f391ad 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -1241,6 +1241,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te mFastScroller.setEnabled(enabled); } else if (enabled) { mFastScroller = new FastScroller(this); + mFastScroller.setEnabled(true); } } diff --git a/core/java/android/widget/FastScroller.java b/core/java/android/widget/FastScroller.java index b3b00b1..393720f 100644 --- a/core/java/android/widget/FastScroller.java +++ b/core/java/android/widget/FastScroller.java @@ -106,6 +106,7 @@ class FastScroller { private final Rect mTempBounds = new Rect(); private final Rect mTempMargins = new Rect(); + private final Rect mContainerRect = new Rect(); private final AbsListView mList; private final ViewGroupOverlay mOverlay; @@ -159,6 +160,9 @@ class FastScroller { private Object[] mSections; + /** Whether this view is currently performing layout. */ + private boolean mUpdatingLayout; + /** * Current decoration state, one of: * <ul> @@ -434,6 +438,16 @@ class FastScroller { * Measures and layouts the scrollbar and decorations. */ private void updateLayout() { + // Prevent re-entry when RTL properties change as a side-effect of + // resolving padding. + if (mUpdatingLayout) { + return; + } + + mUpdatingLayout = true; + + updateContainerRect(); + layoutThumb(); layoutTrack(); @@ -451,6 +465,8 @@ class FastScroller { bounds.bottom += mPreviewImage.getPaddingBottom(); applyLayout(mPreviewImage, bounds); } + + mUpdatingLayout = false; } /** @@ -513,14 +529,15 @@ class FastScroller { marginRight = margins.right; } - final int listWidth = mList.getWidth(); + final Rect container = mContainerRect; + final int containerWidth = container.width(); final int maxWidth; if (adjacent == null) { - maxWidth = listWidth; + maxWidth = containerWidth; } else if (mLayoutFromRight) { maxWidth = adjacent.getLeft(); } else { - maxWidth = listWidth - adjacent.getRight(); + maxWidth = containerWidth - adjacent.getRight(); } final int adjMaxWidth = maxWidth - marginLeft - marginRight; @@ -533,10 +550,10 @@ class FastScroller { final int left; final int right; if (mLayoutFromRight) { - right = (adjacent == null ? listWidth : adjacent.getLeft()) - marginRight; + right = (adjacent == null ? container.right : adjacent.getLeft()) - marginRight; left = right - width; } else { - left = (adjacent == null ? 0 : adjacent.getRight()) + marginLeft; + left = (adjacent == null ? container.left : adjacent.getRight()) + marginLeft; right = left + width; } @@ -560,22 +577,41 @@ class FastScroller { marginRight = margins.right; } - final View list = mList; - final int listWidth = list.getWidth(); - final int adjMaxWidth = listWidth - marginLeft - marginRight; + final Rect container = mContainerRect; + final int containerWidth = container.width(); + final int adjMaxWidth = containerWidth - marginLeft - marginRight; final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(adjMaxWidth, MeasureSpec.AT_MOST); final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); preview.measure(widthMeasureSpec, heightMeasureSpec); // Align at the vertical center, 10% from the top. + final int containerHeight = container.height(); final int width = preview.getMeasuredWidth(); - final int top = list.getHeight() / 10 + marginTop; + final int top = containerHeight / 10 + marginTop + container.top; final int bottom = top + preview.getMeasuredHeight(); - final int left = (listWidth - width) / 2; + final int left = (containerWidth - width) / 2 + container.left; final int right = left + width; out.set(left, top, right, bottom); } + private void updateContainerRect() { + final AbsListView list = mList; + final Rect container = mContainerRect; + container.left = 0; + container.top = 0; + container.right = list.getWidth(); + container.bottom = list.getHeight(); + + final int scrollbarStyle = list.getScrollBarStyle(); + if (scrollbarStyle == View.SCROLLBARS_INSIDE_INSET + || scrollbarStyle == View.SCROLLBARS_INSIDE_OVERLAY) { + container.left += list.getPaddingLeft(); + container.top += list.getPaddingTop(); + container.right -= list.getPaddingRight(); + container.bottom -= list.getPaddingBottom(); + } + } + /** * Lays out the thumb according to the current scrollbar position. */ @@ -586,25 +622,24 @@ class FastScroller { } /** - * Lays out the track centered on the thumb, if available, or against the - * edge if no thumb is available. Must be called after {@link #layoutThumb}. + * Lays out the track centered on the thumb. Must be called after + * {@link #layoutThumb}. */ private void layoutTrack() { final View track = mTrackImage; final View thumb = mThumbImage; - final View list = mList; - final int listWidth = list.getWidth(); - final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(listWidth, MeasureSpec.AT_MOST); + final Rect container = mContainerRect; + final int containerWidth = container.width(); + final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(containerWidth, MeasureSpec.AT_MOST); final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); track.measure(widthMeasureSpec, heightMeasureSpec); final int trackWidth = track.getMeasuredWidth(); final int thumbHalfHeight = thumb == null ? 0 : thumb.getHeight() / 2; - final int left = thumb == null ? listWidth - trackWidth : - thumb.getLeft() + (thumb.getWidth() - trackWidth) / 2; + final int left = thumb.getLeft() + (thumb.getWidth() - trackWidth) / 2; final int right = left + trackWidth; - final int top = thumbHalfHeight; - final int bottom = list.getHeight() - thumbHalfHeight; + final int top = container.top + thumbHalfHeight; + final int bottom = container.bottom - thumbHalfHeight; track.layout(left, top, right, bottom); } @@ -990,36 +1025,40 @@ class FastScroller { * to place the thumb. */ private void setThumbPos(float position) { - final int top = 0; - final int bottom = mList.getHeight(); - - final float thumbHalfHeight = mThumbImage.getHeight() / 2f; - final float min = top + thumbHalfHeight; - final float max = bottom - thumbHalfHeight; + final Rect container = mContainerRect; + final int top = container.top; + final int bottom = container.bottom; + + final ImageView trackImage = mTrackImage; + final ImageView thumbImage = mThumbImage; + final float min = trackImage.getTop(); + final float max = trackImage.getBottom(); final float offset = min; final float range = max - min; final float thumbMiddle = position * range + offset; - mThumbImage.setTranslationY(thumbMiddle - thumbHalfHeight); + thumbImage.setTranslationY(thumbMiddle - thumbImage.getHeight() / 2); // Center the preview on the thumb, constrained to the list bounds. - final float previewHalfHeight = mPreviewImage.getHeight() / 2f; + final ImageView previewImage = mPreviewImage; + final float previewHalfHeight = previewImage.getHeight() / 2f; final float minP = top + previewHalfHeight; final float maxP = bottom - previewHalfHeight; final float previewMiddle = MathUtils.constrain(thumbMiddle, minP, maxP); final float previewTop = previewMiddle - previewHalfHeight; + previewImage.setTranslationY(previewTop); - mPreviewImage.setTranslationY(previewTop); mPrimaryText.setTranslationY(previewTop); mSecondaryText.setTranslationY(previewTop); } private float getPosFromMotionEvent(float y) { - final int top = 0; - final int bottom = mList.getHeight(); + final Rect container = mContainerRect; + final int top = container.top; + final int bottom = container.bottom; - final float thumbHalfHeight = mThumbImage.getHeight() / 2f; - final float min = top + thumbHalfHeight; - final float max = bottom - thumbHalfHeight; + final ImageView trackImage = mTrackImage; + final float min = trackImage.getTop(); + final float max = trackImage.getBottom(); final float offset = min; final float range = max - min; diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index 82b2654..ebd3e1c 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -28,11 +28,13 @@ import com.android.internal.view.IInputMethodClient; /** * Public interface to the global input method manager, used by all client * applications. + * You need to update BridgeIInputMethodManager.java as well when changing + * this file. */ interface IInputMethodManager { List<InputMethodInfo> getInputMethodList(); List<InputMethodInfo> getEnabledInputMethodList(); - List<InputMethodSubtype> getEnabledInputMethodSubtypeList(in InputMethodInfo imi, + List<InputMethodSubtype> getEnabledInputMethodSubtypeList(in String imiId, boolean allowsImplicitlySelectedSubtypes); InputMethodSubtype getLastInputMethodSubtype(); // TODO: We should change the return type from List to List<Parcelable> diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index f8b26bc..a2cc40c 100644 --- a/core/tests/coretests/AndroidManifest.xml +++ b/core/tests/coretests/AndroidManifest.xml @@ -94,7 +94,7 @@ <uses-permission android:name="android.permission.MOVE_PACKAGE" /> <uses-permission android:name="android.permission.PACKAGE_VERIFICATION_AGENT" /> - <!--os storage test permissions --> + <!-- os storage test permissions --> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" /> <uses-permission android:name="android.permission.ASEC_ACCESS" /> <uses-permission android:name="android.permission.ASEC_CREATE" /> @@ -103,6 +103,10 @@ <uses-permission android:name="android.permission.ASEC_RENAME" /> <uses-permission android:name="android.permission.SHUTDOWN" /> + <!-- virtual display test permissions --> + <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT" /> + <uses-permission android:name="android.permission.CAPTURE_SECURE_VIDEO_OUTPUT" /> + <!-- accessibility test permissions --> <uses-permission android:name="android.permission.RETRIEVE_WINDOW_CONTENT" /> diff --git a/core/tests/coretests/src/android/hardware/display/VirtualDisplayTest.java b/core/tests/coretests/src/android/hardware/display/VirtualDisplayTest.java new file mode 100644 index 0000000..ebecf2e --- /dev/null +++ b/core/tests/coretests/src/android/hardware/display/VirtualDisplayTest.java @@ -0,0 +1,508 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.display; + +import android.app.Presentation; +import android.content.Context; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.graphics.Point; +import android.graphics.drawable.ColorDrawable; +import android.hardware.display.DisplayManager; +import android.hardware.display.VirtualDisplay; +import android.media.Image; +import android.media.ImageReader; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.SystemClock; +import android.test.AndroidTestCase; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.Display; +import android.view.Surface; +import android.view.ViewGroup.LayoutParams; +import android.view.WindowManager; +import android.widget.ImageView; + +import java.nio.ByteBuffer; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Tests that applications can create virtual displays and present content on them. + * + * Contains additional tests that cannot be included in CTS because they require + * system permissions. See also the CTS version of VirtualDisplayTest. + */ +public class VirtualDisplayTest extends AndroidTestCase { + private static final String TAG = "VirtualDisplayTest"; + + private static final String NAME = TAG; + private static final int WIDTH = 720; + private static final int HEIGHT = 480; + private static final int DENSITY = DisplayMetrics.DENSITY_MEDIUM; + private static final int TIMEOUT = 10000; + + // Colors that we use as a signal to determine whether some desired content was + // drawn. The colors themselves doesn't matter but we choose them to have with distinct + // values for each color channel so as to detect possible RGBA vs. BGRA buffer format issues. + // We should only observe RGBA buffers but some graphics drivers might incorrectly + // deliver BGRA buffers to virtual displays instead. + private static final int BLUEISH = 0xff1122ee; + private static final int GREENISH = 0xff33dd44; + + private DisplayManager mDisplayManager; + private Handler mHandler; + private final Lock mImageReaderLock = new ReentrantLock(true /*fair*/); + private ImageReader mImageReader; + private Surface mSurface; + private ImageListener mImageListener; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + mDisplayManager = (DisplayManager)mContext.getSystemService(Context.DISPLAY_SERVICE); + mHandler = new Handler(Looper.getMainLooper()); + mImageListener = new ImageListener(); + + mImageReaderLock.lock(); + try { + mImageReader = new ImageReader(WIDTH, HEIGHT, PixelFormat.RGBA_8888, 2); + mImageReader.setImageAvailableListener(mImageListener, mHandler); + mSurface = mImageReader.getSurface(); + } finally { + mImageReaderLock.unlock(); + } + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + + mImageReaderLock.lock(); + try { + mImageReader.close(); + mImageReader = null; + mSurface = null; + } finally { + mImageReaderLock.unlock(); + } + } + + /** + * Ensures that an application can create a private virtual display and show + * its own windows on it. + */ + public void testPrivateVirtualDisplay() throws Exception { + VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME, + WIDTH, HEIGHT, DENSITY, mSurface, 0); + assertNotNull("virtual display must not be null", virtualDisplay); + + Display display = virtualDisplay.getDisplay(); + try { + assertDisplayRegistered(display, Display.FLAG_PRIVATE); + + // Show a private presentation on the display. + assertDisplayCanShowPresentation("private presentation window", + display, BLUEISH, + WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION, 0); + } finally { + virtualDisplay.release(); + } + assertDisplayUnregistered(display); + } + + /** + * Ensures that an application can create a private presentation virtual display and show + * its own windows on it. + */ + public void testPrivatePresentationVirtualDisplay() throws Exception { + VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME, + WIDTH, HEIGHT, DENSITY, mSurface, + DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION); + assertNotNull("virtual display must not be null", virtualDisplay); + + Display display = virtualDisplay.getDisplay(); + try { + assertDisplayRegistered(display, Display.FLAG_PRIVATE | Display.FLAG_PRESENTATION); + + // Show a private presentation on the display. + assertDisplayCanShowPresentation("private presentation window", + display, BLUEISH, + WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION, 0); + } finally { + virtualDisplay.release(); + } + assertDisplayUnregistered(display); + } + + /** + * Ensures that an application can create a public virtual display and show + * its own windows on it. This test requires the CAPTURE_VIDEO_OUTPUT permission. + * + * Because this test does not have an activity token, we use the TOAST window + * type to create the window. Another choice might be SYSTEM_ALERT_WINDOW but + * that requires a permission. + */ + public void testPublicPresentationVirtualDisplay() throws Exception { + VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME, + WIDTH, HEIGHT, DENSITY, mSurface, + DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC + | DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION); + assertNotNull("virtual display must not be null", virtualDisplay); + + Display display = virtualDisplay.getDisplay(); + try { + assertDisplayRegistered(display, Display.FLAG_PRESENTATION); + + // Mirroring case. + // Show a window on the default display. It should be mirrored to the + // virtual display automatically. + Display defaultDisplay = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY); + assertDisplayCanShowPresentation("mirrored window", + defaultDisplay, GREENISH, + WindowManager.LayoutParams.TYPE_TOAST, 0); + + // Mirroring case with secure window (but display is not secure). + // Show a window on the default display. It should be replaced with black on + // the virtual display. + assertDisplayCanShowPresentation("mirrored secure window on non-secure display", + defaultDisplay, Color.BLACK, + WindowManager.LayoutParams.TYPE_TOAST, + WindowManager.LayoutParams.FLAG_SECURE); + + // Presentation case. + // Show a normal presentation on the display. + assertDisplayCanShowPresentation("presentation window", + display, BLUEISH, + WindowManager.LayoutParams.TYPE_TOAST, 0); + + // Presentation case with secure window (but display is not secure). + // Show a normal presentation on the display. It should be replaced with black. + assertDisplayCanShowPresentation("secure presentation window on non-secure display", + display, Color.BLACK, + WindowManager.LayoutParams.TYPE_TOAST, + WindowManager.LayoutParams.FLAG_SECURE); + } finally { + virtualDisplay.release(); + } + assertDisplayUnregistered(display); + } + + /** + * Ensures that an application can create a secure public virtual display and show + * its own windows on it. This test requires the CAPTURE_SECURE_VIDEO_OUTPUT permission. + * + * Because this test does not have an activity token, we use the TOAST window + * type to create the window. Another choice might be SYSTEM_ALERT_WINDOW but + * that requires a permission. + */ + public void testSecurePublicPresentationVirtualDisplay() throws Exception { + VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME, + WIDTH, HEIGHT, DENSITY, mSurface, + DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE + | DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC + | DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION); + assertNotNull("virtual display must not be null", virtualDisplay); + + Display display = virtualDisplay.getDisplay(); + try { + assertDisplayRegistered(display, Display.FLAG_PRESENTATION | Display.FLAG_SECURE); + + // Mirroring case with secure window (and display is secure). + // Show a window on the default display. It should be mirrored to the + // virtual display automatically. + Display defaultDisplay = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY); + assertDisplayCanShowPresentation("mirrored secure window on secure display", + defaultDisplay, GREENISH, + WindowManager.LayoutParams.TYPE_TOAST, + WindowManager.LayoutParams.FLAG_SECURE); + + // Presentation case with secure window (and display is secure). + // Show a normal presentation on the display. + assertDisplayCanShowPresentation("secure presentation window on secure display", + display, BLUEISH, + WindowManager.LayoutParams.TYPE_TOAST, + WindowManager.LayoutParams.FLAG_SECURE); + } finally { + virtualDisplay.release(); + } + assertDisplayUnregistered(display); + } + + private void assertDisplayRegistered(Display display, int flags) { + assertNotNull("display object must not be null", display); + assertTrue("display must be valid", display.isValid()); + assertTrue("display id must be unique", + display.getDisplayId() != Display.DEFAULT_DISPLAY); + assertEquals("display must have correct flags", flags, display.getFlags()); + assertEquals("display name must match supplied name", NAME, display.getName()); + Point size = new Point(); + display.getSize(size); + assertEquals("display width must match supplied width", WIDTH, size.x); + assertEquals("display height must match supplied height", HEIGHT, size.y); + assertEquals("display rotation must be 0", + Surface.ROTATION_0, display.getRotation()); + assertNotNull("display must be registered", + findDisplay(mDisplayManager.getDisplays(), NAME)); + + if ((flags & Display.FLAG_PRESENTATION) != 0) { + assertNotNull("display must be registered as a presentation display", + findDisplay(mDisplayManager.getDisplays( + DisplayManager.DISPLAY_CATEGORY_PRESENTATION), NAME)); + } else { + assertNull("display must not be registered as a presentation display", + findDisplay(mDisplayManager.getDisplays( + DisplayManager.DISPLAY_CATEGORY_PRESENTATION), NAME)); + } + } + + private void assertDisplayUnregistered(Display display) { + assertNull("display must no longer be registered after being removed", + findDisplay(mDisplayManager.getDisplays(), NAME)); + assertFalse("display must no longer be valid", display.isValid()); + } + + private void assertDisplayCanShowPresentation(String message, final Display display, + final int color, final int windowType, final int windowFlags) { + // At this point, we should not have seen any blue. + assertTrue(message + ": display should not show content before window is shown", + mImageListener.getColor() != color); + + final TestPresentation[] presentation = new TestPresentation[1]; + try { + // Show the presentation. + runOnUiThread(new Runnable() { + @Override + public void run() { + presentation[0] = new TestPresentation(getContext(), display, + color, windowType, windowFlags); + presentation[0].show(); + } + }); + + // Wait for the blue to be seen. + assertTrue(message + ": display should show content after window is shown", + mImageListener.waitForColor(color, TIMEOUT)); + } finally { + if (presentation[0] != null) { + runOnUiThread(new Runnable() { + @Override + public void run() { + presentation[0].dismiss(); + } + }); + } + } + } + + private void runOnUiThread(Runnable runnable) { + Runnable waiter = new Runnable() { + @Override + public void run() { + synchronized (this) { + notifyAll(); + } + } + }; + synchronized (waiter) { + mHandler.post(runnable); + mHandler.post(waiter); + try { + waiter.wait(TIMEOUT); + } catch (InterruptedException ex) { + } + } + } + + private Display findDisplay(Display[] displays, String name) { + for (int i = 0; i < displays.length; i++) { + if (displays[i].getName().equals(name)) { + return displays[i]; + } + } + return null; + } + + private final class TestPresentation extends Presentation { + private final int mColor; + private final int mWindowType; + private final int mWindowFlags; + + public TestPresentation(Context context, Display display, + int color, int windowType, int windowFlags) { + super(context, display); + mColor = color; + mWindowType = windowType; + mWindowFlags = windowFlags; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setTitle(TAG); + getWindow().setType(mWindowType); + getWindow().addFlags(mWindowFlags); + + // Create a solid color image to use as the content of the presentation. + ImageView view = new ImageView(getContext()); + view.setImageDrawable(new ColorDrawable(mColor)); + view.setLayoutParams(new LayoutParams( + LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); + setContentView(view); + } + } + + /** + * Watches for an image with a large amount of some particular solid color to be shown. + */ + private final class ImageListener + implements ImageReader.OnImageAvailableListener { + private int mColor = -1; + + public int getColor() { + synchronized (this) { + return mColor; + } + } + + public boolean waitForColor(int color, long timeoutMillis) { + long timeoutTime = SystemClock.uptimeMillis() + timeoutMillis; + synchronized (this) { + while (mColor != color) { + long now = SystemClock.uptimeMillis(); + if (now >= timeoutTime) { + return false; + } + try { + wait(timeoutTime - now); + } catch (InterruptedException ex) { + } + } + return true; + } + } + + @Override + public void onImageAvailable(ImageReader reader) { + mImageReaderLock.lock(); + try { + if (reader != mImageReader) { + return; + } + + Log.d(TAG, "New image available from virtual display."); + Image image = reader.getNextImage(); + if (image != null) { + try { + // Get the latest buffer. + for (;;) { + Image nextImage = reader.getNextImage(); + if (nextImage == null) { + break; + } + reader.releaseImage(image); + image = nextImage; + } + + // Scan for colors. + int color = scanImage(image); + synchronized (this) { + if (mColor != color) { + mColor = color; + notifyAll(); + } + } + } finally { + reader.releaseImage(image); + } + } + } finally { + mImageReaderLock.unlock(); + } + } + + private int scanImage(Image image) { + final Image.Plane plane = image.getPlanes()[0]; + final ByteBuffer buffer = plane.getBuffer(); + final int width = image.getWidth(); + final int height = image.getHeight(); + final int pixelStride = plane.getPixelStride(); + final int rowStride = plane.getRowStride(); + final int rowPadding = rowStride - pixelStride * width; + + Log.d(TAG, "- Scanning image: width=" + width + ", height=" + height + + ", pixelStride=" + pixelStride + ", rowStride=" + rowStride); + + int offset = 0; + int blackPixels = 0; + int bluePixels = 0; + int greenPixels = 0; + int otherPixels = 0; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int pixel = 0; + pixel |= (buffer.get(offset) & 0xff) << 16; // R + pixel |= (buffer.get(offset + 1) & 0xff) << 8; // G + pixel |= (buffer.get(offset + 2) & 0xff); // B + pixel |= (buffer.get(offset + 3) & 0xff) << 24; // A + if (pixel == Color.BLACK || pixel == 0) { + blackPixels += 1; + } else if (pixel == BLUEISH) { + bluePixels += 1; + } else if (pixel == GREENISH) { + greenPixels += 1; + } else { + otherPixels += 1; + if (otherPixels < 10) { + Log.d(TAG, "- Found unexpected color: " + Integer.toHexString(pixel)); + } + } + offset += pixelStride; + } + offset += rowPadding; + } + + // Return a color if it represents more than one quarter of the pixels. + // We use this threshold in case the display is being letterboxed when + // mirroring so there might be large black bars on the sides, which is normal. + Log.d(TAG, "- Pixels: " + blackPixels + " black, " + + bluePixels + " blue, " + + greenPixels + " green, " + + otherPixels + " other"); + final int threshold = width * height / 4; + if (bluePixels > threshold) { + Log.d(TAG, "- Reporting blue."); + return BLUEISH; + } + if (greenPixels > threshold) { + Log.d(TAG, "- Reporting green."); + return GREENISH; + } + if (blackPixels > threshold) { + Log.d(TAG, "- Reporting black."); + return Color.BLACK; + } + Log.d(TAG, "- Reporting other."); + return -1; + } + } +} + |