diff options
72 files changed, 2499 insertions, 458 deletions
@@ -257,7 +257,8 @@ LOCAL_SRC_FILES += \ telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl \ telephony/java/com/android/internal/telephony/IWapPushManager.aidl \ wifi/java/android/net/wifi/IWifiManager.aidl \ - wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl + wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl \ + packages/services/Proxy/com/android/net/IProxyService.aidl \ # FRAMEWORKS_BASE_JAVA_SRC_DIRS comes from build/core/pathmap.mk LOCAL_AIDL_INCLUDES += $(FRAMEWORKS_BASE_JAVA_SRC_DIRS) diff --git a/api/current.txt b/api/current.txt index 6769aa3..b8795f3 100644 --- a/api/current.txt +++ b/api/current.txt @@ -10536,6 +10536,7 @@ package android.hardware { field public static final int TYPE_ALL = -1; // 0xffffffff field public static final int TYPE_AMBIENT_TEMPERATURE = 13; // 0xd field public static final int TYPE_GAME_ROTATION_VECTOR = 15; // 0xf + field public static final int TYPE_GEOMAGNETIC_ROTATION_VECTOR = 20; // 0x14 field public static final int TYPE_GRAVITY = 9; // 0x9 field public static final int TYPE_GYROSCOPE = 4; // 0x4 field public static final int TYPE_GYROSCOPE_UNCALIBRATED = 16; // 0x10 @@ -10549,6 +10550,8 @@ package android.hardware { field public static final int TYPE_RELATIVE_HUMIDITY = 12; // 0xc field public static final int TYPE_ROTATION_VECTOR = 11; // 0xb field public static final int TYPE_SIGNIFICANT_MOTION = 17; // 0x11 + field public static final int TYPE_STEP_COUNTER = 19; // 0x13 + field public static final int TYPE_STEP_DETECTOR = 18; // 0x12 field public static final deprecated int TYPE_TEMPERATURE = 7; // 0x7 } 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; + } + } +} + diff --git a/docs/html/guide/topics/manifest/uses-feature-element.jd b/docs/html/guide/topics/manifest/uses-feature-element.jd index 4c11adc..af35540 100644 --- a/docs/html/guide/topics/manifest/uses-feature-element.jd +++ b/docs/html/guide/topics/manifest/uses-feature-element.jd @@ -163,8 +163,7 @@ feature</em>, if necessary. </li> <dd>The OpenGL ES version required by the application. The higher 16 bits represent the major number and the lower 16 bits represent the minor number. For example, to specify OpenGL ES version 2.0, you would set the value as -"0x00020000". To specify OpenGL ES 2.1, if/when such a version were made -available, you would set the value as "0x00020001". +"0x00020000", or to specify OpenGL ES 3.0, you would set the value as "0x00030000". <p>An application should specify at most one <code>android:glEsVersion</code> attribute in its manifest. If it specifies more than one, the @@ -183,6 +182,10 @@ that it requires OpenGL ES 2.0.</p> <p>An application that can work with any of several OpenGL ES versions should only specify the numerically lowest version of OpenGL ES that it requires. (It can check at run-time whether a higher level of OpenGL ES is available.)</p> + + <p>For more information about using OpenGL ES, including how to check the supported OpenGL ES +version at runtime, see the <a href="{@docRoot}guide/topics/graphics/opengl.html">OpenGL ES</a> +API guide.</p> </dd> </dl> diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp index 0b429f6..cd589de 100644 --- a/media/jni/android_media_ImageReader.cpp +++ b/media/jni/android_media_ImageReader.cpp @@ -701,11 +701,12 @@ static jboolean ImageReader_imageSetup(JNIEnv* env, jobject thiz, } status_t res = consumer->lockNextBuffer(buffer); if (res != NO_ERROR) { - ALOGE("%s Fail to lockNextBuffer with error: %d ", __FUNCTION__, res); + if (res != BAD_VALUE /*no buffers*/) { + ALOGE("%s Fail to lockNextBuffer with error: %d ", __FUNCTION__, res); + } return false; } - // Check if the left-top corner of the crop rect is origin, we currently assume this point is // zero, will revist this once this assumption turns out problematic. Point lt = buffer->crop.leftTop(); diff --git a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java index e19505f..313774b 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java @@ -36,6 +36,8 @@ import android.widget.Toast; import com.android.documentsui.model.Document; +import java.io.FileNotFoundException; + /** * Dialog to create a new directory. */ @@ -73,12 +75,16 @@ public class CreateDirectoryFragment extends DialogFragment { final DocumentsActivity activity = (DocumentsActivity) getActivity(); final Document cwd = activity.getCurrentDirectory(); - final Uri childUri = resolver.insert(cwd.uri, values); - if (childUri != null) { + Uri childUri = resolver.insert(cwd.uri, values); + try { // Navigate into newly created child final Document childDoc = Document.fromUri(resolver, childUri); activity.onDocumentPicked(childDoc); - } else { + } catch (FileNotFoundException e) { + childUri = null; + } + + if (childUri == null) { Toast.makeText(context, R.string.save_error, Toast.LENGTH_SHORT).show(); } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java index a50c312..94c2b61 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java @@ -19,12 +19,14 @@ package com.android.documentsui; import static com.android.documentsui.DirectoryFragment.TYPE_NORMAL; import static com.android.documentsui.DirectoryFragment.TYPE_RECENT_OPEN; import static com.android.documentsui.DirectoryFragment.TYPE_SEARCH; +import static com.android.documentsui.DocumentsActivity.TAG; import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.os.CancellationSignal; +import android.util.Log; import com.android.documentsui.model.Document; import com.android.internal.util.Predicate; @@ -32,6 +34,7 @@ import com.google.android.collect.Lists; import libcore.io.IoUtils; +import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -60,20 +63,24 @@ public class DirectoryLoader extends UriDerivativeLoader<List<Document>> { final Cursor cursor = resolver.query(uri, null, null, null, null, signal); try { while (cursor != null && cursor.moveToNext()) { - final Document doc; + Document doc = null; switch (mType) { case TYPE_NORMAL: case TYPE_SEARCH: doc = Document.fromDirectoryCursor(uri, cursor); break; case TYPE_RECENT_OPEN: - doc = Document.fromRecentOpenCursor(resolver, cursor); + try { + doc = Document.fromRecentOpenCursor(resolver, cursor); + } catch (FileNotFoundException e) { + Log.w(TAG, "Failed to find recent: " + e); + } break; default: throw new IllegalArgumentException("Unknown type"); } - if (mFilter == null || mFilter.apply(doc)) { + if (doc != null && (mFilter == null || mFilter.apply(doc))) { result.add(doc); } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java index fae5673..8e7e087 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java @@ -25,12 +25,10 @@ import android.content.ClipData; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Intent; -import android.content.SharedPreferences; import android.database.Cursor; import android.graphics.drawable.ColorDrawable; import android.net.Uri; import android.os.Bundle; -import android.preference.PreferenceManager; import android.provider.DocumentsContract.DocumentColumns; import android.support.v4.app.ActionBarDrawerToggle; import android.support.v4.view.GravityCompat; @@ -53,6 +51,7 @@ import com.android.documentsui.model.Document; import com.android.documentsui.model.DocumentStack; import com.android.documentsui.model.Root; +import java.io.FileNotFoundException; import java.util.Arrays; import java.util.List; @@ -138,6 +137,8 @@ public class DocumentsActivity extends Activity { cursor.getColumnIndex(RecentsProvider.COL_PATH)); mStack = DocumentStack.deserialize(getContentResolver(), raw); } + } catch (FileNotFoundException e) { + Log.w(TAG, "Failed to resume", e); } finally { cursor.close(); } @@ -470,7 +471,10 @@ public class DocumentsActivity extends Activity { mStack.clear(); if (!root.isRecents) { - onDocumentPicked(Document.fromRoot(getContentResolver(), root)); + try { + onDocumentPicked(Document.fromRoot(getContentResolver(), root)); + } catch (FileNotFoundException e) { + } } else { onCurrentDirectoryChanged(); } diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java index 1e018e7..5cdc915 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java @@ -16,6 +16,8 @@ package com.android.documentsui; +import static com.android.documentsui.DocumentsActivity.TAG; + import android.app.Fragment; import android.app.FragmentManager; import android.app.FragmentTransaction; @@ -28,6 +30,7 @@ import android.net.Uri; import android.os.Bundle; import android.os.CancellationSignal; import android.text.TextUtils.TruncateAt; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -44,6 +47,7 @@ import com.google.android.collect.Lists; import libcore.io.IoUtils; +import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.List; @@ -136,8 +140,12 @@ public class RecentsCreateFragment extends Fragment { while (cursor != null && cursor.moveToNext()) { final String rawStack = cursor.getString( cursor.getColumnIndex(RecentsProvider.COL_PATH)); - final DocumentStack stack = DocumentStack.deserialize(resolver, rawStack); - result.add(stack); + try { + final DocumentStack stack = DocumentStack.deserialize(resolver, rawStack); + result.add(stack); + } catch (FileNotFoundException e) { + Log.w(TAG, "Failed to resolve stack: " + e); + } } } finally { IoUtils.closeQuietly(cursor); diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/Document.java b/packages/DocumentsUI/src/com/android/documentsui/model/Document.java index f274465..95922b4 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/model/Document.java +++ b/packages/DocumentsUI/src/com/android/documentsui/model/Document.java @@ -21,10 +21,12 @@ import android.database.Cursor; import android.net.Uri; import android.provider.DocumentsContract; import android.provider.DocumentsContract.DocumentColumns; -import android.util.Log; import com.android.documentsui.RecentsProvider; +import libcore.io.IoUtils; + +import java.io.FileNotFoundException; import java.util.Comparator; /** @@ -50,7 +52,8 @@ public class Document { this.size = size; } - public static Document fromRoot(ContentResolver resolver, Root root) { + public static Document fromRoot(ContentResolver resolver, Root root) + throws FileNotFoundException { return fromUri(resolver, root.uri); } @@ -70,14 +73,16 @@ public class Document { return new Document(uri, mimeType, displayName, lastModified, flags, summary, size); } - public static Document fromRecentOpenCursor(ContentResolver resolver, Cursor recentCursor) { + public static Document fromRecentOpenCursor(ContentResolver resolver, Cursor recentCursor) + throws FileNotFoundException { final Uri uri = Uri.parse(getCursorString(recentCursor, RecentsProvider.COL_URI)); final long lastModified = getCursorLong(recentCursor, RecentsProvider.COL_TIMESTAMP); - final Cursor cursor = resolver.query(uri, null, null, null, null); + Cursor cursor = null; try { + cursor = resolver.query(uri, null, null, null, null); if (!cursor.moveToFirst()) { - throw new IllegalArgumentException("Missing details for " + uri); + throw new FileNotFoundException("Missing details for " + uri); } final String mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE); final String displayName = getCursorString(cursor, DocumentColumns.DISPLAY_NAME); @@ -87,16 +92,19 @@ public class Document { final long size = getCursorLong(cursor, DocumentColumns.SIZE); return new Document(uri, mimeType, displayName, lastModified, flags, summary, size); + } catch (Throwable t) { + throw asFileNotFoundException(t); } finally { - cursor.close(); + IoUtils.closeQuietly(cursor); } } - public static Document fromUri(ContentResolver resolver, Uri uri) { - final Cursor cursor = resolver.query(uri, null, null, null, null); + public static Document fromUri(ContentResolver resolver, Uri uri) throws FileNotFoundException { + Cursor cursor = null; try { + cursor = resolver.query(uri, null, null, null, null); if (!cursor.moveToFirst()) { - throw new IllegalArgumentException("Missing details for " + uri); + throw new FileNotFoundException("Missing details for " + uri); } final String mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE); final String displayName = getCursorString(cursor, DocumentColumns.DISPLAY_NAME); @@ -106,8 +114,10 @@ public class Document { final long size = getCursorLong(cursor, DocumentColumns.SIZE); return new Document(uri, mimeType, displayName, lastModified, flags, summary, size); + } catch (Throwable t) { + throw asFileNotFoundException(t); } finally { - cursor.close(); + IoUtils.closeQuietly(cursor); } } @@ -174,4 +184,14 @@ public class Document { return Long.compare(rhs.size, lhs.size); } } + + public static FileNotFoundException asFileNotFoundException(Throwable t) + throws FileNotFoundException { + if (t instanceof FileNotFoundException) { + throw (FileNotFoundException) t; + } + final FileNotFoundException fnfe = new FileNotFoundException(t.getMessage()); + fnfe.initCause(t); + throw fnfe; + } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java index 67dca07..d6c852e 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java +++ b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java @@ -17,6 +17,7 @@ package com.android.documentsui.model; import static com.android.documentsui.DocumentsActivity.TAG; +import static com.android.documentsui.model.Document.asFileNotFoundException; import android.content.ContentResolver; import android.net.Uri; @@ -25,6 +26,7 @@ import android.util.Log; import org.json.JSONArray; import org.json.JSONException; +import java.io.FileNotFoundException; import java.util.LinkedList; /** @@ -41,7 +43,8 @@ public class DocumentStack extends LinkedList<Document> { return json.toString(); } - public static DocumentStack deserialize(ContentResolver resolver, String raw) { + public static DocumentStack deserialize(ContentResolver resolver, String raw) + throws FileNotFoundException { Log.d(TAG, "deserialize: " + raw); final DocumentStack stack = new DocumentStack(); @@ -53,7 +56,7 @@ public class DocumentStack extends LinkedList<Document> { stack.add(doc); } } catch (JSONException e) { - Log.w(TAG, "Failed to decode stack", e); + throw asFileNotFoundException(e); } // TODO: handle roots that have gone missing diff --git a/packages/PrintSpooler/res/layout/print_job_config_activity_container.xml b/packages/PrintSpooler/res/layout/print_job_config_activity_container.xml index e36b8b5..a0c111b 100644 --- a/packages/PrintSpooler/res/layout/print_job_config_activity_container.xml +++ b/packages/PrintSpooler/res/layout/print_job_config_activity_container.xml @@ -19,7 +19,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" - android:background="@color/print_job_config_activity_content_background"> + android:background="@color/container_background"> <include layout="@layout/print_job_config_activity_content_editing"> diff --git a/packages/PrintSpooler/res/layout/print_job_config_activity_content_editing.xml b/packages/PrintSpooler/res/layout/print_job_config_activity_content_editing.xml index 77ef5a2..84c41de 100644 --- a/packages/PrintSpooler/res/layout/print_job_config_activity_content_editing.xml +++ b/packages/PrintSpooler/res/layout/print_job_config_activity_content_editing.xml @@ -18,19 +18,22 @@ android:id="@+id/content_editing" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:orientation="vertical" - android:divider="?android:attr/dividerHorizontal" - android:showDividers="middle"> + android:orientation="vertical"> <ScrollView android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" - android:scrollbars="vertical"> + android:scrollbars="vertical" + android:background="@color/editable_background"> <GridLayout android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_marginStart="24dip" + android:layout_marginTop="32dip" + android:layout_marginEnd="24dip" + android:layout_marginBottom="24dip" android:orientation="vertical" android:columnCount="2"> @@ -41,14 +44,10 @@ android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_gravity="fill_horizontal" - android:layout_marginLeft="32dip" - android:layout_marginTop="32dip" - android:layout_marginRight="32dip" - android:layout_marginBottom="12dip" android:layout_row="0" android:layout_column="0" android:layout_columnSpan="2" - android:minHeight="?android:attr/listPreferredItemHeight"> + android:minHeight="?android:attr/listPreferredItemHeightSmall"> </Spinner> <!-- Copies --> @@ -58,31 +57,28 @@ android:id="@+id/copies_edittext" android:layout_width="fill_parent" android:layout_height="wrap_content" - android:layout_marginLeft="32dip" - android:layout_marginRight="12dip" - android:layout_marginBottom="12dip" + android:layout_marginEnd="6dip" android:layout_row="2" android:layout_column="0" android:layout_gravity="bottom" android:inputType="numberDecimal" android:selectAllOnFocus="true" android:minWidth="150dip" - android:minHeight="?android:attr/listPreferredItemHeight"> + android:minHeight="?android:attr/listPreferredItemHeightSmall"> </view> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginLeft="32dip" android:layout_marginTop="12dip" - android:layout_marginRight="12dip" + android:layout_marginStart="12dip" + android:layout_marginEnd="6dip" android:layout_row="1" android:layout_column="0" - android:layout_gravity="left|bottom" + android:layout_gravity="start|bottom" + android:labelFor="@id/copies_edittext" android:text="@string/label_copies" - android:textAppearance="?android:attr/textAppearanceSmall" - android:textStyle="bold" - android:labelFor="@id/copies_edittext"> + android:textAppearance="@style/PrintOptionTitleTextAppearance"> </TextView> <!-- Paper size --> @@ -91,27 +87,22 @@ android:id="@+id/paper_size_spinner" android:layout_width="fill_parent" android:layout_height="wrap_content" - android:layout_marginLeft="12dip" - android:layout_marginRight="32dip" - android:layout_marginBottom="12dip" + android:layout_marginStart="6dip" android:layout_row="2" android:layout_column="1" - android:minWidth="150dip" - android:minHeight="?android:attr/listPreferredItemHeight"> + style="@style/PrintOptionSpinnerStyle"> </Spinner> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginLeft="12dip" - android:layout_marginRight="32dip" android:layout_marginTop="12dip" + android:layout_marginStart="18dip" android:layout_row="1" android:layout_column="1" + android:labelFor="@id/paper_size_spinner" android:text="@string/label_paper_size" - android:textAppearance="?android:attr/textAppearanceSmall" - android:textStyle="bold" - android:labelFor="@id/paper_size_spinner"> + android:textAppearance="@style/PrintOptionTitleTextAppearance"> </TextView> <!-- Color --> @@ -120,27 +111,23 @@ android:id="@+id/color_spinner" android:layout_width="fill_parent" android:layout_height="wrap_content" - android:layout_marginLeft="32dip" - android:layout_marginRight="12dip" - android:layout_marginBottom="12dip" + android:layout_marginEnd="6dip" android:layout_row="4" android:layout_column="0" - android:minWidth="150dip" - android:minHeight="?android:attr/listPreferredItemHeight"> + style="@style/PrintOptionSpinnerStyle"> </Spinner> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginLeft="32dip" android:layout_marginTop="12dip" - android:layout_marginRight="12dip" + android:layout_marginStart="12dip" + android:layout_marginEnd="6dip" android:layout_row="3" android:layout_column="0" + android:labelFor="@id/color_spinner" android:text="@string/label_color" - android:textAppearance="?android:attr/textAppearanceSmall" - android:textStyle="bold" - android:labelFor="@id/color_spinner"> + android:textAppearance="@style/PrintOptionTitleTextAppearance"> </TextView> <!-- Orientation --> @@ -149,27 +136,22 @@ android:id="@+id/orientation_spinner" android:layout_width="fill_parent" android:layout_height="wrap_content" - android:layout_marginLeft="12dip" - android:layout_marginRight="32dip" - android:layout_marginBottom="12dip" + android:layout_marginStart="6dip" android:layout_row="4" android:layout_column="1" - android:minWidth="150dip" - android:minHeight="?android:attr/listPreferredItemHeight"> + style="@style/PrintOptionSpinnerStyle"> </Spinner> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginLeft="12dip" android:layout_marginTop="12dip" - android:layout_marginRight="32dip" + android:layout_marginStart="18dip" android:layout_row="3" android:layout_column="1" + android:labelFor="@id/orientation_spinner" android:text="@string/label_orientation" - android:textAppearance="?android:attr/textAppearanceSmall" - android:textStyle="bold" - android:labelFor="@id/orientation_spinner"> + android:textAppearance="@style/PrintOptionTitleTextAppearance"> </TextView> <!-- Pages --> @@ -178,12 +160,10 @@ android:id="@+id/range_options_spinner" android:layout_width="fill_parent" android:layout_height="wrap_content" - android:layout_marginLeft="32dip" - android:layout_marginRight="12dip" + android:layout_marginEnd="6dip" android:layout_row="6" android:layout_column="0" - android:minWidth="150dip" - android:minHeight="?android:attr/listPreferredItemHeight"> + style="@style/PrintOptionSpinnerStyle"> </Spinner> <view @@ -191,8 +171,8 @@ android:id="@+id/page_range_edittext" android:layout_width="fill_parent" android:layout_height="wrap_content" - android:layout_marginLeft="12dip" - android:layout_marginRight="32dip" + android:layout_marginTop="12dip" + android:layout_marginStart="6dip" android:layout_row="6" android:layout_column="1" android:layout_gravity="bottom" @@ -201,81 +181,41 @@ android:hint="@string/pages_range_example" android:inputType="textNoSuggestions" android:visibility="gone" - android:minHeight="?android:attr/listPreferredItemHeight"> + android:minHeight="?android:attr/listPreferredItemHeightSmall"> </view> <TextView android:id="@+id/page_range_title" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginLeft="32dip" android:layout_marginTop="12dip" - android:layout_marginRight="12dip" + android:layout_marginStart="12dip" android:layout_row="5" android:layout_column="0" + android:labelFor="@id/range_options_spinner" android:text="@string/label_pages" - android:textAppearance="?android:attr/textAppearanceSmall" - android:textStyle="bold" - android:labelFor="@id/range_options_spinner"> + android:textAppearance="@style/PrintOptionTitleTextAppearance"> </TextView> - <!-- Print pereview --> - - <ImageView - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:layout_gravity="fill_horizontal" - android:layout_marginLeft="32dip" - android:layout_marginTop="32dip" - android:layout_marginRight="32dip" - android:layout_row="7" - android:layout_column="0" - android:layout_columnSpan="2" - android:background="?android:attr/listDivider" - android:contentDescription="@null"> - </ImageView> - - <Button - android:id="@+id/print_preview_button" - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:layout_gravity="fill_horizontal" - android:layout_marginLeft="32dip" - android:layout_marginRight="32dip" - android:layout_row="8" - android:layout_column="0" - android:layout_columnSpan="2" - android:text="@string/print_preview" - android:gravity="left|center_vertical" - android:background="?android:attr/selectableItemBackground" - android:minHeight="?android:attr/listPreferredItemHeight"> - </Button> - - <ImageView - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:layout_gravity="fill_horizontal" - android:layout_marginLeft="32dip" - android:layout_marginRight="32dip" - android:layout_marginBottom="32dip" - android:layout_row="9" - android:layout_column="0" - android:layout_columnSpan="2" - android:background="?android:attr/listDivider" - android:contentDescription="@null"> - </ImageView> - </GridLayout> </ScrollView> + <View + android:layout_width="fill_parent" + android:layout_height="1dip" + android:background="@color/separator"> + </View> + <Button android:id="@+id/print_button" + style="?android:attr/buttonBarButtonStyle" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_gravity="fill_horizontal" android:text="@string/print_button" - style="?android:attr/buttonBarButtonStyle"> + android:textSize="16sp" + android:textColor="@color/important_text"> </Button> </LinearLayout> diff --git a/packages/PrintSpooler/res/layout/print_job_config_activity_content_generating.xml b/packages/PrintSpooler/res/layout/print_job_config_activity_content_generating.xml index 6352afc..8bdb6c9 100644 --- a/packages/PrintSpooler/res/layout/print_job_config_activity_content_generating.xml +++ b/packages/PrintSpooler/res/layout/print_job_config_activity_content_generating.xml @@ -18,35 +18,56 @@ android:id="@+id/content_generating" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:orientation="vertical" - android:divider="?android:attr/dividerHorizontal" - android:showDividers="middle"> + android:orientation="vertical"> - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - style="?android:attr/buttonBarButtonStyle" - android:singleLine="true" - android:ellipsize="end" - android:textAlignment="viewStart" - android:text="@string/generating_print_job" > - </TextView> - - <ProgressBar - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_margin="32dip" - android:layout_gravity="center_horizontal" - style="?android:attr/progressBarStyleLarge"> - </ProgressBar> + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:background="@color/editable_background" + android:orientation="vertical"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="16dip" + android:layout_marginEnd="16dip" + android:layout_gravity="center" + style="?android:attr/buttonBarButtonStyle" + android:singleLine="true" + android:ellipsize="end" + android:text="@string/generating_print_job" + android:textColor="@color/important_text" + android:textSize="16sp"> + </TextView> + + <ProgressBar + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="32dip" + android:layout_marginEnd="32dip" + android:layout_marginTop="16dip" + android:layout_marginBottom="32dip" + android:layout_gravity="center_horizontal" + style="?android:attr/progressBarStyleLarge"> + </ProgressBar> + + <View + android:layout_width="fill_parent" + android:layout_height="1dip" + android:background="@color/separator"> + </View> + + </LinearLayout> <Button android:id="@+id/cancel_button" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_gravity="fill_horizontal" - android:text="@string/cancel_button" - style="?android:attr/buttonBarButtonStyle"> + style="?android:attr/buttonBarButtonStyle" + android:text="@string/cancel" + android:textSize="16sp" + android:textColor="@color/important_text"> </Button> </LinearLayout> diff --git a/packages/PrintSpooler/res/layout/spinner_dropdown_item.xml b/packages/PrintSpooler/res/layout/spinner_dropdown_item.xml index 66c6724..002cc14 100644 --- a/packages/PrintSpooler/res/layout/spinner_dropdown_item.xml +++ b/packages/PrintSpooler/res/layout/spinner_dropdown_item.xml @@ -17,11 +17,11 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" - android:paddingLeft="8dip" - android:paddingRight="8dip" + android:paddingStart="8dip" + android:paddingEnd="8dip" android:minHeight="?android:attr/listPreferredItemHeightSmall" android:orientation="vertical" - android:gravity="center_vertical"> + android:gravity="left|center_vertical"> <TextView android:id="@+id/title" @@ -30,7 +30,9 @@ android:textAppearance="?android:attr/textAppearanceMedium" android:singleLine="true" android:ellipsize="end" - android:textIsSelectable="false"> + android:textIsSelectable="false" + android:gravity="top|left" + android:textColor="@color/important_text"> </TextView> <TextView @@ -41,7 +43,8 @@ android:singleLine="true" android:ellipsize="end" android:textIsSelectable="false" - android:visibility="gone"> + android:visibility="gone" + android:textColor="@color/important_text"> </TextView> diff --git a/packages/PrintSpooler/res/values/colors.xml b/packages/PrintSpooler/res/values/colors.xml index 3d8ba22..9972c96 100644 --- a/packages/PrintSpooler/res/values/colors.xml +++ b/packages/PrintSpooler/res/values/colors.xml @@ -15,5 +15,11 @@ --> <resources> - <color name="print_job_config_activity_content_background">#FFF2F2F2</color> + + <color name="container_background">#FFFFFF</color> + <color name="important_text">#333333</color> + <color name="print_option_title">#888888</color> + <color name="separator">#CCCCCC</color> + <color name="editable_background">#F2F2F2</color> + </resources>
\ No newline at end of file diff --git a/packages/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml index 8391509..2086f58 100644 --- a/packages/PrintSpooler/res/values/strings.xml +++ b/packages/PrintSpooler/res/values/strings.xml @@ -20,10 +20,7 @@ <string name="app_label">Print Spooler</string> <!-- Label of the print dialog's print button. [CHAR LIMIT=16] --> - <string name="print_button">PRINT</string> - - <!-- Label of the print dialog's cancel button. [CHAR LIMIT=16] --> - <string name="cancel_button">CANCEL</string> + <string name="print_button">Print</string> <!-- Label of the destination widget. [CHAR LIMIT=20] --> <string name="label_destination">DESTIINATION</string> diff --git a/packages/PrintSpooler/res/values/styles.xml b/packages/PrintSpooler/res/values/styles.xml new file mode 100644 index 0000000..702adf4 --- /dev/null +++ b/packages/PrintSpooler/res/values/styles.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<resources> + + <style name="PrintOptionTitleTextAppearance"> + <item name="android:textStyle">normal</item> + <item name="android:textSize">14sp</item> + <item name="android:textAllCaps">true</item> + <item name="android:textColor">@color/print_option_title</item> + </style> + + <style name="PrintOptionSpinnerStyle"> + <item name="android:paddingTop">0dip</item> + <item name="android:paddingBottom">0dip</item> + <item name="android:minWidth">150dip</item> + <item name="android:minHeight">?android:attr/listPreferredItemHeightSmall</item> + </style> + +</resources> diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java index 824815f..654bb6d 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java +++ b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java @@ -19,7 +19,6 @@ package com.android.printspooler; import android.app.Activity; import android.app.Dialog; import android.content.Context; -import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; @@ -56,6 +55,7 @@ import android.view.MotionEvent; import android.view.View; import android.view.View.MeasureSpec; import android.view.View.OnClickListener; +import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; @@ -66,7 +66,6 @@ import android.widget.Button; import android.widget.EditText; import android.widget.Spinner; import android.widget.TextView; -import android.widget.Toast; import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -104,7 +103,7 @@ public class PrintJobConfigActivity extends Activity { private static final int EDITOR_STATE_INITIALIZED = 1; private static final int EDITOR_STATE_CONFIRMED_PRINT = 2; - private static final int EDITOR_STATE_CONFIRMED_PREVIEW = 3; +// private static final int EDITOR_STATE_CONFIRMED_PREVIEW = 3; private static final int EDITOR_STATE_CANCELLED = 4; private static final int MIN_COPIES = 1; @@ -214,7 +213,7 @@ public class PrintJobConfigActivity extends Activity { public boolean onTouchEvent(MotionEvent event) { if (!mEditor.isPrintConfirmed() && !mEditor.isPreviewConfirmed() - && getWindow().shouldCloseOnTouch(this, event)) { + && mEditor.shouldCloseOnTouch(event)) { if (!mController.isWorking()) { PrintJobConfigActivity.this.finish(); } @@ -606,7 +605,7 @@ public class PrintJobConfigActivity extends Activity { private final SimpleStringSplitter mStringCommaSplitter = new SimpleStringSplitter(','); - private final Button mPrintPreviewButton; + private final View mContentContainer; private final Button mPrintButton; @@ -802,6 +801,9 @@ public class PrintJobConfigActivity extends Activity { private boolean mIgnoreNextRangeChange; public Editor() { + // Content container + mContentContainer = findViewById(R.id.content_container); + // Copies mCopiesEditText = (EditText) findViewById(R.id.copies_edittext); mCopiesEditText.setText(String.valueOf(MIN_COPIES)); @@ -864,19 +866,6 @@ public class PrintJobConfigActivity extends Activity { mRangeOptionsSpinner.setSelection(0); } - // Preview button - mPrintPreviewButton = (Button) findViewById(R.id.print_preview_button); - mPrintPreviewButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - mEditor.confirmPreview(); - // TODO: Implement me - Toast.makeText(PrintJobConfigActivity.this, - "Stop poking me! Not implemented yet :)", - Toast.LENGTH_LONG).show(); - } - }); - // Print button mPrintButton = (Button) findViewById(R.id.print_button); mPrintButton.setOnClickListener(new OnClickListener() { @@ -892,6 +881,32 @@ public class PrintJobConfigActivity extends Activity { updateUi(); } + public boolean shouldCloseOnTouch(MotionEvent event) { + if (event.getAction() != MotionEvent.ACTION_DOWN) { + return false; + } + + final int[] locationInWindow = new int[2]; + mContentContainer.getLocationInWindow(locationInWindow); + + final int windowTouchSlop = ViewConfiguration.get(PrintJobConfigActivity.this) + .getScaledWindowTouchSlop(); + final int eventX = (int) event.getX(); + final int eventY = (int) event.getY(); + final int lenientWindowLeft = locationInWindow[0] - windowTouchSlop; + final int lenientWindowRight = lenientWindowLeft + mContentContainer.getWidth() + + windowTouchSlop; + final int lenientWindowTop = locationInWindow[1] - windowTouchSlop; + final int lenientWindowBottom = lenientWindowTop + mContentContainer.getHeight() + + windowTouchSlop; + + if (eventX < lenientWindowLeft || eventX > lenientWindowRight + || eventY < lenientWindowTop || eventY > lenientWindowBottom) { + return true; + } + return false; + } + public boolean isShwoingGeneratingPrintJobUi() { return (findViewById(R.id.content_generating) != null); } @@ -917,7 +932,7 @@ public class PrintJobConfigActivity extends Activity { }); // First animation - fade out the old content. - contentEditing.animate().alpha(0.0f).withEndAction(new Runnable() { + contentEditing.animate().alpha(0.0f).withLayer().withEndAction(new Runnable() { @Override public void run() { contentEditing.setVisibility(View.INVISIBLE); @@ -936,7 +951,7 @@ public class PrintJobConfigActivity extends Activity { / (float) contentContainer.getHeight(); // Second animation - resize the container. - contentContainer.animate().scaleY(scaleY).withEndAction( + contentContainer.animate().scaleY(scaleY).withLayer().withEndAction( new Runnable() { @Override public void run() { @@ -946,7 +961,7 @@ public class PrintJobConfigActivity extends Activity { contentContainer.addView(contentGenerating); // Third animation - show the new content. - contentGenerating.animate().alpha(1.0f); + contentGenerating.animate().withLayer().alpha(1.0f); } }); } @@ -987,9 +1002,9 @@ public class PrintJobConfigActivity extends Activity { return mEditorState == EDITOR_STATE_CONFIRMED_PRINT; } - public void confirmPreview() { - mEditorState = EDITOR_STATE_CONFIRMED_PREVIEW; - } +// public void confirmPreview() { +// mEditorState = EDITOR_STATE_CONFIRMED_PREVIEW; +// } public PageRange[] getRequestedPages() { if (hasErrors()) { @@ -1035,7 +1050,8 @@ public class PrintJobConfigActivity extends Activity { mOrientationSpinner.setEnabled(false); mRangeOptionsSpinner.setEnabled(false); mRangeEditText.setEnabled(false); - mPrintPreviewButton.setEnabled(false); + // TODO: Remove entirely or implement print preview. +// mPrintPreviewButton.setEnabled(false); mPrintButton.setEnabled(false); return; } @@ -1092,9 +1108,9 @@ public class PrintJobConfigActivity extends Activity { mRangeEditText.setEnabled(false); mRangeEditText.setVisibility(View.INVISIBLE); - // Print preview - mPrintPreviewButton.setEnabled(false); - mPrintPreviewButton.setText(getString(R.string.print_preview)); +// // Print preview +// mPrintPreviewButton.setEnabled(false); +// mPrintPreviewButton.setText(getString(R.string.print_preview)); // Print mPrintButton.setEnabled(false); @@ -1291,15 +1307,15 @@ public class PrintJobConfigActivity extends Activity { && (TextUtils.isEmpty(mRangeEditText.getText()) || hasErrors())) || (mRangeOptionsSpinner.getSelectedItemPosition() == 0 && (!mController.hasPerformedLayout() || hasErrors()))) { - mPrintPreviewButton.setEnabled(false); +// mPrintPreviewButton.setEnabled(false); mPrintButton.setEnabled(false); } else { - mPrintPreviewButton.setEnabled(true); - if (hasPdfViewer()) { - mPrintPreviewButton.setText(getString(R.string.print_preview)); - } else { - mPrintPreviewButton.setText(getString(R.string.install_for_print_preview)); - } +// mPrintPreviewButton.setEnabled(true); +// if (hasPdfViewer()) { +// mPrintPreviewButton.setText(getString(R.string.print_preview)); +// } else { +// mPrintPreviewButton.setText(getString(R.string.install_for_print_preview)); +// } mPrintButton.setEnabled(true); } @@ -1412,12 +1428,12 @@ public class PrintJobConfigActivity extends Activity { || mCopiesEditText.getError() != null; } - private boolean hasPdfViewer() { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setType("application/pdf"); - return !getPackageManager().queryIntentActivities(intent, - PackageManager.MATCH_DEFAULT_ONLY).isEmpty(); - } +// private boolean hasPdfViewer() { +// Intent intent = new Intent(Intent.ACTION_VIEW); +// intent.setType("application/pdf"); +// return !getPackageManager().queryIntentActivities(intent, +// PackageManager.MATCH_DEFAULT_ONLY).isEmpty(); +// } private final class SpinnerItem<T> { final T value; diff --git a/packages/services/PacProcessor/Android.mk b/packages/services/PacProcessor/Android.mk new file mode 100644 index 0000000..e4afde6 --- /dev/null +++ b/packages/services/PacProcessor/Android.mk @@ -0,0 +1,41 @@ +# +# Copyright (C) 2010 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. +# + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + main_pacserver.cpp \ + ProxyService.cpp \ + IProxyService.cpp + +LOCAL_C_INCLUDES += \ + external/chromium-libpac/src + +LOCAL_SHARED_LIBRARIES := \ + libutils \ + liblog \ + libpac \ + libbinder \ + libstlport + +LOCAL_MODULE := pacserver +LOCAL_MODULE_TAGS := optional + +include external/stlport/libstlport.mk + +include $(BUILD_EXECUTABLE) diff --git a/packages/services/PacProcessor/IProxyService.cpp b/packages/services/PacProcessor/IProxyService.cpp new file mode 100644 index 0000000..3707d85 --- /dev/null +++ b/packages/services/PacProcessor/IProxyService.cpp @@ -0,0 +1,97 @@ +#define LOG_TAG "ProxyTesting" + +#include <stdint.h> +#include <sys/types.h> +#include <binder/Parcel.h> +#include <binder/IPCThreadState.h> +#include <utils/Errors.h> +#include "IProxyService.h" + +#include <utils/Log.h> + +#include <private/android_filesystem_config.h> + +using namespace android; + +String16 BpProxyService::resolveProxies(String16 host, String16 url) { + String16 ret; + return ret; +} + +void BpProxyService::setPacFile(String16& scriptContents) { + +} + +void BpProxyService::startPacSystem() { + +} +void BpProxyService::stopPacSystem() { + +} + +IMPLEMENT_META_INTERFACE(ProxyService, "com.android.net.IProxyService"); + +status_t BnProxyService::onTransact( + uint32_t code, const Parcel& data, + Parcel* reply, uint32_t flags) { + int returnInt = 0; + switch (code) { + case RESOLVE_PROXIES: + { + CHECK_INTERFACE(IProxyService, data, reply); + String16 host = data.readString16(); + String16 url = data.readString16(); + String16 response = resolveProxies(host, url); + reply->writeNoException(); + reply->writeString16(response); + return NO_ERROR; + } break; + case SET_PAC: + { + CHECK_INTERFACE(IProxyService, data, reply); + if (notSystemUid()) { + returnInt = 1; + } else { + String16 pacFile = data.readString16(); + setPacFile(pacFile); + } + reply->writeNoException(); + reply->writeInt32(returnInt); + return NO_ERROR; + } break; + case START_PAC: + { + CHECK_INTERFACE(IProxyService, data, reply); + if (notSystemUid()) { + returnInt = 1; + } else { + startPacSystem(); + } + reply->writeNoException(); + reply->writeInt32(returnInt); + return NO_ERROR; + } break; + case STOP_PAC: + { + CHECK_INTERFACE(IProxyService, data, reply); + if (notSystemUid()) { + returnInt = 1; + } else { + stopPacSystem(); + } + reply->writeNoException(); + reply->writeInt32(returnInt); + return NO_ERROR; + } break; + default: + return BBinder::onTransact(code, data, reply, flags); + } +} + +int BnProxyService::getCallingUid() { + return IPCThreadState::self()->getCallingUid(); +} + +bool BnProxyService::notSystemUid() { + return getCallingUid() != AID_SYSTEM; +} diff --git a/packages/services/PacProcessor/IProxyService.h b/packages/services/PacProcessor/IProxyService.h new file mode 100644 index 0000000..57c527b --- /dev/null +++ b/packages/services/PacProcessor/IProxyService.h @@ -0,0 +1,59 @@ +#ifndef IPROXY_SERVICE_H +#define IPROXY_SERVICE_H + +#include <binder/IInterface.h> +#include <binder/IBinder.h> + +namespace android { +class IProxyService : public IInterface { +public: + /** + * Keep up-to-date with + * frameworks/base/packages/services/Proxy/com/android/net/IProxyService.aidl + */ + enum { + RESOLVE_PROXIES = IBinder::FIRST_CALL_TRANSACTION, + SET_PAC, + START_PAC, + STOP_PAC, + }; +public: + DECLARE_META_INTERFACE(ProxyService); + +public: + + virtual String16 resolveProxies(String16 host, String16 url) = 0; + + virtual void setPacFile(String16& scriptContents) = 0; + + virtual void startPacSystem() = 0; + virtual void stopPacSystem() = 0; +private: +}; + +class BpProxyService : public BpInterface<IProxyService> { +public: + BpProxyService(const sp<IBinder>& impl) : BpInterface<IProxyService>(impl) {} + + virtual String16 resolveProxies(String16 host, String16 url); + + virtual void setPacFile(String16& scriptContents); + + virtual void startPacSystem(); + virtual void stopPacSystem(); +}; + +class BnProxyService : public BnInterface<IProxyService> { +public: + virtual status_t onTransact( + uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0); + +private: + int getCallingUid(); + + bool notSystemUid(); +}; +} + + +#endif //IPROXY_SERVICE_H diff --git a/packages/services/PacProcessor/ProxyService.cpp b/packages/services/PacProcessor/ProxyService.cpp new file mode 100644 index 0000000..7084a47 --- /dev/null +++ b/packages/services/PacProcessor/ProxyService.cpp @@ -0,0 +1,96 @@ +#define LOG_TAG "ProxyService" +#include <utils/Log.h> + +#include <errno.h> +#include <utils/threads.h> +#include <binder/IServiceManager.h> +#include <binder/IPCThreadState.h> +#include <sys/stat.h> +#include <proxy_resolver_v8.h> +#include <sstream> + +#include "ProxyService.h" + +using namespace net; + +using namespace android; + +class ProxyErrorLogger : public ProxyErrorListener { +protected: + ~ProxyErrorLogger() { + + } +public: + void AlertMessage(String16 message) { + String8 str(message); + ALOGD("Alert: %s", str.string()); + } + void ErrorMessage(String16 message) { + String8 str(message); + ALOGE("Error: %s", str.string()); + } +}; + +void ProxyService::instantiate() { + ALOGV("instantiate"); + defaultServiceManager()->addService(String16("com.android.net.IProxyService"), + new ProxyService()); +} + +ProxyService::ProxyService() { + hasSetScript = false; +} + +ProxyService::~ProxyService() { + stopPacSystem(); +} + +String16 ProxyService::resolveProxies(String16 host, String16 url) { + ALOGV("resolve"); + String16 blankRet; + if (proxyResolver != NULL) { + if (hasSetScript) { + String16 ret; + if (proxyResolver->GetProxyForURL(url, host, &ret) != OK) { + return blankRet; + } + return ret; + } else { + ALOGD("Unable to resolve PAC when no script is set!"); + } + } else { + ALOGE("Cannot parse while resolver not initialized!"); + } + return blankRet; +} + +void ProxyService::setPacFile(String16& scriptContents) { + ALOGV("set"); + if (proxyResolver != NULL) { + if (proxyResolver->SetPacScript(scriptContents) != OK) { + ALOGD("Unable to initialize PAC - Resolving will not work"); + } else { + hasSetScript = true; + } + } else { + ALOGE("PAC script set while resolver not initialized!"); + } +} + +void ProxyService::startPacSystem() { + ALOGV("start"); + // Stop in case redundant start call + stopPacSystem(); + + proxyResolver = new ProxyResolverV8(ProxyResolverJSBindings::CreateDefault(), + new ProxyErrorLogger()); + hasSetScript = false; +} + +void ProxyService::stopPacSystem() { + ALOGV("stop"); + if (proxyResolver != NULL) { + delete proxyResolver; + proxyResolver = NULL; + } +} diff --git a/packages/services/PacProcessor/ProxyService.h b/packages/services/PacProcessor/ProxyService.h new file mode 100644 index 0000000..a0861b2 --- /dev/null +++ b/packages/services/PacProcessor/ProxyService.h @@ -0,0 +1,33 @@ +#ifndef PROXY_SERVICE_H +#define PROXY_SERVICE_H + +#include <binder/IInterface.h> +#include "IProxyService.h" +#include "proxy_resolver_v8.h" + +namespace android { + +class ProxyService : public BnProxyService { +public: + static void instantiate(); + +private: + ProxyService(); + virtual ~ProxyService(); + +public: + String16 resolveProxies(String16 host, String16 url); + + void setPacFile(String16& scriptContents); + + void startPacSystem(); + void stopPacSystem(); + +private: + net::ProxyResolverV8* proxyResolver; + bool hasSetScript; +}; + +} + +#endif //PROXY_SERVICE_H diff --git a/packages/services/PacProcessor/main_pacserver.cpp b/packages/services/PacProcessor/main_pacserver.cpp new file mode 100644 index 0000000..19588b5 --- /dev/null +++ b/packages/services/PacProcessor/main_pacserver.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2010 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. + */ + +#define LOG_TAG "pacserver" +//#define LOG_NDEBUG 0 + +#include <binder/IPCThreadState.h> +#include <binder/ProcessState.h> +#include <binder/IServiceManager.h> +#include <utils/Log.h> +#include "ProxyService.h" +#include "proxy_resolver_v8.h" +#include <stdio.h> + +using namespace android; + +int main(int argc, char** argv) +{ + sp<ProcessState> proc(ProcessState::self()); + sp<IServiceManager> sm = defaultServiceManager(); + + printf("1\n"); + ALOGV("ServiceManager: %p", sm.get()); + ProxyService::instantiate(); + printf("1\n"); + + ProcessState::self()->startThreadPool(); + printf("1\n"); + IPCThreadState::self()->joinThreadPool(); +} diff --git a/packages/services/Proxy/Android.mk b/packages/services/Proxy/Android.mk new file mode 100644 index 0000000..d5546b2 --- /dev/null +++ b/packages/services/Proxy/Android.mk @@ -0,0 +1,14 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := optional + +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_PACKAGE_NAME := ProxyHandler +LOCAL_CERTIFICATE := platform +LOCAL_PRIVILEGED_MODULE := true + +include $(BUILD_PACKAGE) + +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/packages/services/Proxy/AndroidManifest.xml b/packages/services/Proxy/AndroidManifest.xml new file mode 100644 index 0000000..02475c0 --- /dev/null +++ b/packages/services/Proxy/AndroidManifest.xml @@ -0,0 +1,25 @@ +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + package="com.android.proxyhandler" + coreApp="true"> + + <uses-permission android:name="android.permission.INTERNET" /> + + <application + android:persistent="true" + android:label="@string/app_label" + android:process="com.android.proxyhandler"> + + <service android:name=".ProxyService" + android:exported="true"> + </service> + + <receiver android:name=".ProxyServiceReceiver"> + + <intent-filter> + <action android:name="android.intent.action.PROXY_CHANGE" /> + </intent-filter> + </receiver> + + </application> +</manifest> diff --git a/packages/services/Proxy/com/android/net/IProxyService.aidl b/packages/services/Proxy/com/android/net/IProxyService.aidl new file mode 100644 index 0000000..7e9ed79 --- /dev/null +++ b/packages/services/Proxy/com/android/net/IProxyService.aidl @@ -0,0 +1,16 @@ +package com.android.net; + +/** @hide */ +interface IProxyService +{ + /** + * Keep up-to-date with + * frameworks/base/packages/services/PacProcessor/IProxyService.h + */ + String resolvePacFile(String host, String url); + + int setPacFile(String scriptContents); + + int startPacSystem(); + int stopPacSystem(); +} diff --git a/packages/services/Proxy/res/values/strings.xml b/packages/services/Proxy/res/values/strings.xml new file mode 100644 index 0000000..6188d79 --- /dev/null +++ b/packages/services/Proxy/res/values/strings.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright (c) 2009, 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. + */ +--> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="app_label">ProxyHandler</string> + +</resources> diff --git a/packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java b/packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java new file mode 100644 index 0000000..5d8689e --- /dev/null +++ b/packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java @@ -0,0 +1,219 @@ + +package com.android.proxyhandler; + +import android.net.ProxyProperties; +import android.util.Log; + +import com.google.android.collect.Lists; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.ProxySelector; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * @hide + */ +public class ProxyServer extends Thread { + + private static final String CONNECT = "CONNECT"; + private static final String HTTP_OK = "HTTP/1.1 200 OK\n"; + + private static final String TAG = "ProxyServer"; + + private ExecutorService threadExecutor; + + public boolean mIsRunning = false; + + private ServerSocket serverSocket; + private ProxyProperties mProxy; + + private class ProxyConnection implements Runnable { + private Socket connection; + + private ProxyConnection(Socket connection) { + this.connection = connection; + } + + @Override + public void run() { + try { + android.net.Proxy.setHttpProxySystemProperty(mProxy); + + String requestLine = getLine(connection.getInputStream()); + if (requestLine == null) { + connection.close(); + return; + } + String[] splitLine = requestLine.split(" "); + if (splitLine.length < 3) { + connection.close(); + return; + } + String requestType = splitLine[0]; + String urlString = splitLine[1]; + + String host = ""; + int port = 80; + + if (requestType.equals(CONNECT)) { + String[] hostPortSplit = urlString.split(":"); + host = hostPortSplit[0]; + try { + port = Integer.parseInt(hostPortSplit[1]); + } catch (NumberFormatException nfe) { + port = 443; + } + urlString = "Https://" + host + ":" + port; + } else { + try { + URI url = new URI(urlString); + host = url.getHost(); + port = url.getPort(); + if (port < 0) { + port = 80; + } + } catch (URISyntaxException e) { + connection.close(); + return; + } + } + + List<Proxy> list = Lists.newArrayList(); + try { + list = ProxySelector.getDefault().select(new URI(urlString)); + } catch (URISyntaxException e) { + e.printStackTrace(); + } + Socket server = null; + for (Proxy proxy : list) { + try { + if (!proxy.equals(Proxy.NO_PROXY)) { + // Only Inets created by PacProxySelector. + InetSocketAddress inetSocketAddress = + (InetSocketAddress)list.get(0).address(); + server = new Socket(inetSocketAddress.getAddress(), + inetSocketAddress.getPort()); + sendLine(server, requestLine); + } else { + server = new Socket(host, port); + if (requestType.equals(CONNECT)) { + while (getLine(connection.getInputStream()).length() != 0); + // No proxy to respond so we must. + sendLine(connection, HTTP_OK); + } else { + sendLine(server, requestLine); + } + } + } catch (IOException ioe) { + + } + if (server != null) { + break; + } + } + if (server == null) { + server = new Socket(host, port); + if (requestType.equals(CONNECT)) { + while (getLine(connection.getInputStream()).length() != 0); + // No proxy to respond so we must. + sendLine(connection, HTTP_OK); + } else { + sendLine(server, requestLine); + } + } + // Pass data back and forth until complete. + SocketConnect.connect(connection, server); + } catch (IOException e) { + Log.d(TAG, "Problem Proxying", e); + } + try { + connection.close(); + } catch (IOException ioe) { + + } + } + + private String getLine(InputStream inputStream) throws IOException { + StringBuffer buffer = new StringBuffer(); + int byteBuffer = inputStream.read(); + if (byteBuffer < 0) return ""; + do { + if (byteBuffer != '\r') { + buffer.append((char)byteBuffer); + } + byteBuffer = inputStream.read(); + } while ((byteBuffer != '\n') && (byteBuffer >= 0)); + + return buffer.toString(); + } + + private void sendLine(Socket socket, String line) throws IOException { + OutputStream os = socket.getOutputStream(); + os.write(line.getBytes()); + os.write('\r'); + os.write('\n'); + os.flush(); + } + } + + public ProxyServer() { + threadExecutor = Executors.newCachedThreadPool(); + } + + @Override + public void run() { + try { + serverSocket = new ServerSocket(ProxyService.PORT); + + serverSocket.setReuseAddress(true); + + while (mIsRunning) { + try { + ProxyConnection parser = new ProxyConnection(serverSocket.accept()); + + threadExecutor.execute(parser); + } catch (IOException e) { + e.printStackTrace(); + } + } + } catch (SocketException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + + mIsRunning = false; + } + + public synchronized void startServer() { + mIsRunning = true; + start(); + } + + public synchronized void stopServer() { + mIsRunning = false; + if (serverSocket != null) { + try { + serverSocket.close(); + serverSocket = null; + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + public void setProxy(ProxyProperties proxy) { + mProxy = proxy; + } +} diff --git a/packages/services/Proxy/src/com/android/proxyhandler/ProxyService.java b/packages/services/Proxy/src/com/android/proxyhandler/ProxyService.java new file mode 100644 index 0000000..0aea5ee --- /dev/null +++ b/packages/services/Proxy/src/com/android/proxyhandler/ProxyService.java @@ -0,0 +1,68 @@ +package com.android.proxyhandler; + +import android.app.Service; +import android.content.Intent; +import android.net.Proxy; +import android.net.ProxyProperties; +import android.os.Bundle; +import android.os.IBinder; +import android.text.TextUtils; + +/** + * @hide + */ +public class ProxyService extends Service { + + private static ProxyServer server = null; + + /** Keep these values up-to-date with PacManager.java */ + public static final String KEY_PROXY = "keyProxy"; + public static final String HOST = "localhost"; + public static final int PORT = 8182; + public static final String EXCL_LIST = ""; + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if (intent != null) { + handleCommand(intent); + } + return START_STICKY; + } + + private void handleCommand(Intent intent) { + Bundle bundle = intent.getExtras(); + ProxyProperties proxy = null; + if ((bundle != null) && bundle.containsKey(Proxy.EXTRA_PROXY_INFO)) { + proxy = bundle.getParcelable(Proxy.EXTRA_PROXY_INFO); + if ((proxy != null) && !TextUtils.isEmpty(proxy.getPacFileUrl())) { + startProxy(proxy); + } else { + stopSelf(); + } + } else { + stopSelf(); + } + } + + + private void startProxy(ProxyProperties proxy) { + if (server == null) { + server = new ProxyServer(); + server.startServer(); + } + server.setProxy(proxy); + } + + @Override + public void onDestroy() { + if (server != null) { + server.stopServer(); + server = null; + } + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } +}
\ No newline at end of file diff --git a/packages/services/Proxy/src/com/android/proxyhandler/ProxyServiceReceiver.java b/packages/services/Proxy/src/com/android/proxyhandler/ProxyServiceReceiver.java new file mode 100644 index 0000000..f5c2ca5 --- /dev/null +++ b/packages/services/Proxy/src/com/android/proxyhandler/ProxyServiceReceiver.java @@ -0,0 +1,22 @@ +package com.android.proxyhandler; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.net.Proxy; +import android.os.Bundle; + +public class ProxyServiceReceiver extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + Intent service = new Intent(context, ProxyService.class); + Bundle bundle = intent.getExtras(); + if (bundle != null) { + service.putExtra(Proxy.EXTRA_PROXY_INFO, + bundle.getParcelable(Proxy.EXTRA_PROXY_INFO)); + } + context.startService(service); + } + +} diff --git a/packages/services/Proxy/src/com/android/proxyhandler/SocketConnect.java b/packages/services/Proxy/src/com/android/proxyhandler/SocketConnect.java new file mode 100644 index 0000000..0d7df7f --- /dev/null +++ b/packages/services/Proxy/src/com/android/proxyhandler/SocketConnect.java @@ -0,0 +1,59 @@ +package com.android.proxyhandler; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; + +/** + * @hide + */ +public class SocketConnect extends Thread { + + private InputStream from; + private OutputStream to; + + public SocketConnect(Socket from, Socket to) throws IOException { + this.from = from.getInputStream(); + this.to = to.getOutputStream(); + start(); + } + + @Override + public void run() { + final byte[] buffer = new byte[512]; + + try { + while (true) { + int r = from.read(buffer); + if (r < 0) { + break; + } + to.write(buffer, 0, r); + } + from.close(); + to.close(); + } catch (IOException io) { + + } + } + + public static void connect(Socket first, Socket second) { + try { + SocketConnect sc1 = new SocketConnect(first, second); + SocketConnect sc2 = new SocketConnect(second, first); + try { + sc1.join(); + } catch (InterruptedException e) { + } + try { + sc2.join(); + } catch (InterruptedException e) { + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + +} diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java index f83b017..ae7120f 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java @@ -995,8 +995,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { @Override public void setInitialDisplaySize(Display display, int width, int height, int density) { - if (display.getDisplayId() != Display.DEFAULT_DISPLAY) { - throw new IllegalArgumentException("Can only set the default display"); + // This method might be called before the policy has been fully initialized + // or for other displays we don't care about. + if (mContext == null || display.getDisplayId() != Display.DEFAULT_DISPLAY) { + return; } mDisplay = display; diff --git a/services/input/InputDispatcher.cpp b/services/input/InputDispatcher.cpp index 23a846b..32247e7 100644 --- a/services/input/InputDispatcher.cpp +++ b/services/input/InputDispatcher.cpp @@ -84,6 +84,8 @@ const nsecs_t STREAM_AHEAD_EVENT_TIMEOUT = 500 * 1000000LL; // 0.5sec // Log a warning when an event takes longer than this to process, even if an ANR does not occur. const nsecs_t SLOW_EVENT_PROCESSING_WARNING_TIMEOUT = 2000 * 1000000LL; // 2sec +// Number of recent events to keep for debugging purposes. +const size_t RECENT_QUEUE_MAX_SIZE = 10; static inline nsecs_t now() { return systemTime(SYSTEM_TIME_MONOTONIC); @@ -455,6 +457,14 @@ bool InputDispatcher::enqueueInboundEventLocked(EventEntry* entry) { return needWake; } +void InputDispatcher::addRecentEventLocked(EventEntry* entry) { + entry->refCount += 1; + mRecentQueue.enqueueAtTail(entry); + if (mRecentQueue.count() > RECENT_QUEUE_MAX_SIZE) { + mRecentQueue.dequeueAtHead()->release(); + } +} + sp<InputWindowHandle> InputDispatcher::findTouchedWindowAtLocked(int32_t displayId, int32_t x, int32_t y) { // Traverse windows from front to back to find touched window. @@ -624,6 +634,7 @@ void InputDispatcher::releaseInboundEventLocked(EventEntry* entry) { if (entry == mNextUnblockedEvent) { mNextUnblockedEvent = NULL; } + addRecentEventLocked(entry); entry->release(); } @@ -3161,6 +3172,31 @@ void InputDispatcher::dumpDispatchStateLocked(String8& dump) { nsecs_t currentTime = now(); + // Dump recently dispatched or dropped events from oldest to newest. + if (!mRecentQueue.isEmpty()) { + dump.appendFormat(INDENT "RecentQueue: length=%u\n", mRecentQueue.count()); + for (EventEntry* entry = mRecentQueue.head; entry; entry = entry->next) { + dump.append(INDENT2); + entry->appendDescription(dump); + dump.appendFormat(", age=%0.1fms\n", + (currentTime - entry->eventTime) * 0.000001f); + } + } else { + dump.append(INDENT "RecentQueue: <empty>\n"); + } + + // Dump event currently being dispatched. + if (mPendingEvent) { + dump.append(INDENT "PendingEvent:\n"); + dump.append(INDENT2); + mPendingEvent->appendDescription(dump); + dump.appendFormat(", age=%0.1fms\n", + (currentTime - mPendingEvent->eventTime) * 0.000001f); + } else { + dump.append(INDENT "PendingEvent: <none>\n"); + } + + // Dump inbound events from oldest to newest. if (!mInboundQueue.isEmpty()) { dump.appendFormat(INDENT "InboundQueue: length=%u\n", mInboundQueue.count()); for (EventEntry* entry = mInboundQueue.head; entry; entry = entry->next) { @@ -3809,7 +3845,8 @@ InputDispatcher::ConfigurationChangedEntry::~ConfigurationChangedEntry() { } void InputDispatcher::ConfigurationChangedEntry::appendDescription(String8& msg) const { - msg.append("ConfigurationChangedEvent()"); + msg.append("ConfigurationChangedEvent(), policyFlags=0x%08x", + policyFlags); } @@ -3824,7 +3861,8 @@ InputDispatcher::DeviceResetEntry::~DeviceResetEntry() { } void InputDispatcher::DeviceResetEntry::appendDescription(String8& msg) const { - msg.appendFormat("DeviceResetEvent(deviceId=%d)", deviceId); + msg.appendFormat("DeviceResetEvent(deviceId=%d), policyFlags=0x%08x", + deviceId, policyFlags); } @@ -3846,8 +3884,11 @@ InputDispatcher::KeyEntry::~KeyEntry() { } void InputDispatcher::KeyEntry::appendDescription(String8& msg) const { - msg.appendFormat("KeyEvent(action=%d, deviceId=%d, source=0x%08x)", - action, deviceId, source); + msg.appendFormat("KeyEvent(deviceId=%d, source=0x%08x, action=%d, " + "flags=0x%08x, keyCode=%d, scanCode=%d, metaState=0x%08x, " + "repeatCount=%d), policyFlags=0x%08x", + deviceId, source, action, flags, keyCode, scanCode, metaState, + repeatCount, policyFlags); } void InputDispatcher::KeyEntry::recycle() { @@ -3884,8 +3925,19 @@ InputDispatcher::MotionEntry::~MotionEntry() { } void InputDispatcher::MotionEntry::appendDescription(String8& msg) const { - msg.appendFormat("MotionEvent(action=%d, deviceId=%d, source=0x%08x, displayId=%d)", - action, deviceId, source, displayId); + msg.appendFormat("MotionEvent(deviceId=%d, source=0x%08x, action=%d, " + "flags=0x%08x, metaState=0x%08x, buttonState=0x%08x, edgeFlags=0x%08x, " + "xPrecision=%.1f, yPrecision=%.1f, displayId=%d, pointers=[", + deviceId, source, action, flags, metaState, buttonState, edgeFlags, + xPrecision, yPrecision, displayId); + for (uint32_t i = 0; i < pointerCount; i++) { + if (i) { + msg.append(", "); + } + msg.appendFormat("%d: (%.1f, %.1f)", pointerProperties[i].id, + pointerCoords[i].getX(), pointerCoords[i].getY()); + } + msg.appendFormat("]), policyFlags=0x%08x", policyFlags); } diff --git a/services/input/InputDispatcher.h b/services/input/InputDispatcher.h index 63ea781..0273dc4 100644 --- a/services/input/InputDispatcher.h +++ b/services/input/InputDispatcher.h @@ -846,6 +846,7 @@ private: EventEntry* mPendingEvent; Queue<EventEntry> mInboundQueue; + Queue<EventEntry> mRecentQueue; Queue<CommandEntry> mCommandQueue; void dispatchOnceInnerLocked(nsecs_t* nextWakeupTime); @@ -856,6 +857,9 @@ private: // Cleans up input state when dropping an inbound event. void dropInboundEventLocked(EventEntry* entry, DropReason dropReason); + // Adds an event to a queue of recent events for debugging purposes. + void addRecentEventLocked(EventEntry* entry); + // App switch latency optimization. bool mAppSwitchSawKeyDown; nsecs_t mAppSwitchDueTime; diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java index 17ae85f..3ae2eb5 100644 --- a/services/java/com/android/server/ConnectivityService.java +++ b/services/java/com/android/server/ConnectivityService.java @@ -56,13 +56,11 @@ import android.net.INetworkPolicyManager; import android.net.INetworkStatsService; import android.net.LinkAddress; import android.net.LinkProperties; -import android.net.Uri; import android.net.LinkProperties.CompareResult; import android.net.MobileDataStateTracker; import android.net.NetworkConfig; import android.net.NetworkInfo; import android.net.NetworkInfo.DetailedState; -import android.net.NetworkInfo.State; import android.net.NetworkQuotaInfo; import android.net.NetworkState; import android.net.NetworkStateTracker; @@ -70,6 +68,7 @@ import android.net.NetworkUtils; import android.net.Proxy; import android.net.ProxyProperties; import android.net.RouteInfo; +import android.net.Uri; import android.net.wifi.WifiStateTracker; import android.net.wimax.WimaxManagerConstants; import android.os.AsyncTask; @@ -102,6 +101,7 @@ import android.util.SparseIntArray; import android.util.Xml; import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; import com.android.internal.net.LegacyVpnInfo; import com.android.internal.net.VpnConfig; import com.android.internal.net.VpnProfile; @@ -110,9 +110,11 @@ import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneConstants; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.XmlUtils; +import com.android.net.IProxyService; import com.android.server.am.BatteryStatsService; import com.android.server.connectivity.DataConnectionStats; import com.android.server.connectivity.Nat464Xlat; +import com.android.server.connectivity.PacManager; import com.android.server.connectivity.Tethering; import com.android.server.connectivity.Vpn; import com.android.server.net.BaseNetworkObserver; @@ -120,8 +122,6 @@ import com.android.server.net.LockdownVpnTracker; import com.google.android.collect.Lists; import com.google.android.collect.Sets; -import com.android.internal.annotations.GuardedBy; - import dalvik.system.DexClassLoader; import org.xmlpull.v1.XmlPullParser; @@ -370,6 +370,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { // track the global proxy. private ProxyProperties mGlobalProxy = null; + private PacManager mPacManager = null; + private SettingsObserver mSettingsObserver; NetworkConfig[] mNetConfigs; @@ -631,6 +633,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { mDataConnectionStats = new DataConnectionStats(mContext); mDataConnectionStats.startMonitoring(); + + mPacManager = new PacManager(mContext); } /** @@ -3168,13 +3172,15 @@ public class ConnectivityService extends IConnectivityManager.Stub { // of proxy info to all the JVMs. // enforceAccessPermission(); synchronized (mProxyLock) { - if (mGlobalProxy != null) return mGlobalProxy; - return (mDefaultProxyDisabled ? null : mDefaultProxy); + ProxyProperties ret = mGlobalProxy; + if ((ret == null) && !mDefaultProxyDisabled) ret = mDefaultProxy; + return ret; } } public void setGlobalProxy(ProxyProperties proxyProperties) { enforceConnectivityInternalPermission(); + synchronized (mProxyLock) { if (proxyProperties == mGlobalProxy) return; if (proxyProperties != null && proxyProperties.equals(mGlobalProxy)) return; @@ -3183,11 +3189,16 @@ public class ConnectivityService extends IConnectivityManager.Stub { String host = ""; int port = 0; String exclList = ""; - if (proxyProperties != null && !TextUtils.isEmpty(proxyProperties.getHost())) { + String pacFileUrl = ""; + if (proxyProperties != null && (!TextUtils.isEmpty(proxyProperties.getHost()) || + !TextUtils.isEmpty(proxyProperties.getPacFileUrl()))) { mGlobalProxy = new ProxyProperties(proxyProperties); host = mGlobalProxy.getHost(); port = mGlobalProxy.getPort(); exclList = mGlobalProxy.getExclusionList(); + if (proxyProperties.getPacFileUrl() != null) { + pacFileUrl = proxyProperties.getPacFileUrl(); + } } else { mGlobalProxy = null; } @@ -3198,6 +3209,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { Settings.Global.putInt(res, Settings.Global.GLOBAL_HTTP_PROXY_PORT, port); Settings.Global.putString(res, Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST, exclList); + Settings.Global.putString(res, Settings.Global.GLOBAL_HTTP_PROXY_PAC, pacFileUrl); } finally { Binder.restoreCallingIdentity(token); } @@ -3215,8 +3227,14 @@ public class ConnectivityService extends IConnectivityManager.Stub { int port = Settings.Global.getInt(res, Settings.Global.GLOBAL_HTTP_PROXY_PORT, 0); String exclList = Settings.Global.getString(res, Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST); - if (!TextUtils.isEmpty(host)) { - ProxyProperties proxyProperties = new ProxyProperties(host, port, exclList); + String pacFileUrl = Settings.Global.getString(res, Settings.Global.GLOBAL_HTTP_PROXY_PAC); + if (!TextUtils.isEmpty(host) || !TextUtils.isEmpty(pacFileUrl)) { + ProxyProperties proxyProperties; + if (!TextUtils.isEmpty(pacFileUrl)) { + proxyProperties = new ProxyProperties(pacFileUrl); + } else { + proxyProperties = new ProxyProperties(host, port, exclList); + } synchronized (mProxyLock) { mGlobalProxy = proxyProperties; } @@ -3234,7 +3252,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { } private void handleApplyDefaultProxy(ProxyProperties proxy) { - if (proxy != null && TextUtils.isEmpty(proxy.getHost())) { + if (proxy != null && TextUtils.isEmpty(proxy.getHost()) + && TextUtils.isEmpty(proxy.getPacFileUrl())) { proxy = null; } synchronized (mProxyLock) { @@ -3276,6 +3295,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { private void sendProxyBroadcast(ProxyProperties proxy) { if (proxy == null) proxy = new ProxyProperties("", 0, ""); + mPacManager.setCurrentProxyScriptUrl(proxy); if (DBG) log("sending Proxy Broadcast for " + proxy); Intent intent = new Intent(Proxy.PROXY_CHANGE_ACTION); intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING | diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java index da584e2..f442f11 100644 --- a/services/java/com/android/server/InputMethodManagerService.java +++ b/services/java/com/android/server/InputMethodManagerService.java @@ -969,19 +969,25 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } /** - * @param imi if null, returns enabled subtypes for the current imi + * @param imiId if null, returns enabled subtypes for the current imi * @return enabled subtypes of the specified imi */ @Override - public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(InputMethodInfo imi, + public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId, boolean allowsImplicitlySelectedSubtypes) { // TODO: Make this work even for non-current users? if (!calledFromValidUser()) { - return Collections.emptyList(); + return Collections.<InputMethodSubtype>emptyList(); } synchronized (mMethodMap) { - if (imi == null && mCurMethodId != null) { + final InputMethodInfo imi; + if (imiId == null && mCurMethodId != null) { imi = mMethodMap.get(mCurMethodId); + } else { + imi = mMethodMap.get(imiId); + } + if (imi == null) { + return Collections.<InputMethodSubtype>emptyList(); } return mSettings.getEnabledInputMethodSubtypeListLocked( mContext, imi, allowsImplicitlySelectedSubtypes); diff --git a/services/java/com/android/server/am/ActiveServices.java b/services/java/com/android/server/am/ActiveServices.java index 3548e8c..b96cf92 100644 --- a/services/java/com/android/server/am/ActiveServices.java +++ b/services/java/com/android/server/am/ActiveServices.java @@ -832,8 +832,8 @@ public final class ActiveServices { + " requires " + r.permission); return new ServiceLookupResult(null, r.permission); } - if (!mAm.mIntentFirewall.checkService(service, callingUid, callingPid, resolvedType, - r.appInfo)) { + if (!mAm.mIntentFirewall.checkService(r.name, service, callingUid, callingPid, + resolvedType, r.appInfo)) { return null; } return new ServiceLookupResult(r, null); diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index 1d26c2c..2d2d468 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -1184,17 +1184,19 @@ public final class ActivityManagerService extends ActivityManagerNative String host = ""; String port = ""; String exclList = ""; + String pacFileUrl = null; if (proxy != null) { host = proxy.getHost(); port = Integer.toString(proxy.getPort()); exclList = proxy.getExclusionList(); + pacFileUrl = proxy.getPacFileUrl(); } synchronized (ActivityManagerService.this) { for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) { ProcessRecord r = mLruProcesses.get(i); if (r.thread != null) { try { - r.thread.setHttpProxy(host, port, exclList); + r.thread.setHttpProxy(host, port, exclList, pacFileUrl); } catch (RemoteException ex) { Slog.w(TAG, "Failed to update http proxy for: " + r.info.processName); @@ -12371,7 +12373,7 @@ public final class ActivityManagerService extends ActivityManagerNative Intent intent = (Intent)allSticky.get(i); BroadcastQueue queue = broadcastQueueForIntent(intent); BroadcastRecord r = new BroadcastRecord(queue, intent, null, - null, -1, -1, null, AppOpsManager.OP_NONE, receivers, null, 0, + null, -1, -1, null, null, AppOpsManager.OP_NONE, receivers, null, 0, null, null, false, true, true, -1); queue.enqueueParallelBroadcastLocked(r); queue.scheduleBroadcastsLocked(); @@ -12804,8 +12806,8 @@ public final class ActivityManagerService extends ActivityManagerNative // components to be launched. final BroadcastQueue queue = broadcastQueueForIntent(intent); BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, - callerPackage, callingPid, callingUid, requiredPermission, appOp, - registeredReceivers, resultTo, resultCode, resultData, map, + callerPackage, callingPid, callingUid, resolvedType, requiredPermission, + appOp, registeredReceivers, resultTo, resultCode, resultData, map, ordered, sticky, false, userId); if (DEBUG_BROADCAST) Slog.v( TAG, "Enqueueing parallel broadcast " + r); @@ -12894,9 +12896,9 @@ public final class ActivityManagerService extends ActivityManagerNative || resultTo != null) { BroadcastQueue queue = broadcastQueueForIntent(intent); BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, - callerPackage, callingPid, callingUid, requiredPermission, appOp, - receivers, resultTo, resultCode, resultData, map, ordered, - sticky, false, userId); + callerPackage, callingPid, callingUid, resolvedType, + requiredPermission, appOp, receivers, resultTo, resultCode, + resultData, map, ordered, sticky, false, userId); if (DEBUG_BROADCAST) Slog.v( TAG, "Enqueueing ordered broadcast " + r + ": prev had " + queue.mOrderedBroadcasts.size()); diff --git a/services/java/com/android/server/am/BroadcastQueue.java b/services/java/com/android/server/am/BroadcastQueue.java index cb4b8ff..254a219 100644 --- a/services/java/com/android/server/am/BroadcastQueue.java +++ b/services/java/com/android/server/am/BroadcastQueue.java @@ -421,6 +421,10 @@ public final class BroadcastQueue { skip = true; } } + if (!skip) { + skip = !mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid, + r.callingPid, r.resolvedType, filter.receiverList.uid); + } if (!skip) { // If this is not being sent as an ordered broadcast, then we @@ -729,6 +733,10 @@ public final class BroadcastQueue { skip = true; } } + if (!skip) { + skip = !mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid, + r.callingPid, r.resolvedType, info.activityInfo.applicationInfo.uid); + } boolean isSingleton = false; try { isSingleton = mService.isSingleton(info.activityInfo.processName, diff --git a/services/java/com/android/server/am/BroadcastRecord.java b/services/java/com/android/server/am/BroadcastRecord.java index eb1df6e..db61e88 100644 --- a/services/java/com/android/server/am/BroadcastRecord.java +++ b/services/java/com/android/server/am/BroadcastRecord.java @@ -47,6 +47,7 @@ final class BroadcastRecord extends Binder { final boolean sticky; // originated from existing sticky data? final boolean initialSticky; // initial broadcast from register to sticky? final int userId; // user id this broadcast was for + final String resolvedType; // the resolved data type final String requiredPermission; // a permission the caller has required final int appOp; // an app op that is associated with this broadcast final List receivers; // contains BroadcastFilter and ResolveInfo @@ -171,8 +172,8 @@ final class BroadcastRecord extends Binder { BroadcastRecord(BroadcastQueue _queue, Intent _intent, ProcessRecord _callerApp, String _callerPackage, - int _callingPid, int _callingUid, String _requiredPermission, int _appOp, - List _receivers, IIntentReceiver _resultTo, int _resultCode, + int _callingPid, int _callingUid, String _resolvedType, String _requiredPermission, + int _appOp, List _receivers, IIntentReceiver _resultTo, int _resultCode, String _resultData, Bundle _resultExtras, boolean _serialized, boolean _sticky, boolean _initialSticky, int _userId) { @@ -183,6 +184,7 @@ final class BroadcastRecord extends Binder { callerPackage = _callerPackage; callingPid = _callingPid; callingUid = _callingUid; + resolvedType = _resolvedType; requiredPermission = _requiredPermission; appOp = _appOp; receivers = _receivers; diff --git a/services/java/com/android/server/connectivity/PacManager.java b/services/java/com/android/server/connectivity/PacManager.java new file mode 100644 index 0000000..189c626 --- /dev/null +++ b/services/java/com/android/server/connectivity/PacManager.java @@ -0,0 +1,226 @@ +package com.android.server.connectivity; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.ConnectivityManager; +import android.net.ProxyProperties; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.os.SystemProperties; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.Log; + +import com.android.net.IProxyService; + + +import org.apache.http.HttpEntity; +import org.apache.http.HttpException; +import org.apache.http.HttpHost; +import org.apache.http.HttpRequest; +import org.apache.http.HttpResponse; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.conn.params.ConnRouteParams; +import org.apache.http.conn.routing.HttpRoute; +import org.apache.http.conn.routing.HttpRoutePlanner; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.protocol.HttpContext; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.InetAddress; +import java.net.ProxySelector; +import java.net.URL; +import java.net.URLConnection; + +/** + * @hide + */ +public class PacManager implements Runnable { + public static final int NO_ERROR = 0; + public static final int PERMISSION_DENIED = 1; + public static final String PROXY_SERVICE = "com.android.net.IProxyService"; + + + private static final String TAG = "PACManager"; + + private static final String ACTION_PAC_REFRESH = "android.net.proxy.PAC_REFRESH"; + + private static final String DEFAULT_DELAYS = "8 32 120 14400 43200"; + private static final int DELAY_1 = 0; + private static final int DELAY_4 = 3; + private static final int DELAY_LONG = 4; + + /** Keep these values up-to-date with ProxyService.java */ + public static final String KEY_PROXY = "keyProxy"; + private String mCurrentPac; + private volatile String mPacUrl; + + private AlarmManager mAlarmManager; + private IProxyService mProxyService; + private PendingIntent mPacRefreshIntent; + private Context mContext; + + private int mCurrentDelay; + + class PacRefreshIntentReceiver extends BroadcastReceiver { + public void onReceive(Context context, Intent intent) { + new Thread(PacManager.this).start(); + } + } + + public PacManager(Context context) { + mContext = context; + mProxyService = IProxyService.Stub.asInterface( + ServiceManager.getService(PROXY_SERVICE)); + + mPacRefreshIntent = PendingIntent.getBroadcast( + context, 0, new Intent(ACTION_PAC_REFRESH), 0); + context.registerReceiver(new PacRefreshIntentReceiver(), + new IntentFilter(ACTION_PAC_REFRESH)); + } + + private AlarmManager getAlarmManager() { + if (mAlarmManager == null) { + mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE); + } + return mAlarmManager; + } + + public void setCurrentProxyScriptUrl(ProxyProperties proxy) { + if (!TextUtils.isEmpty(proxy.getPacFileUrl())) { + try { + mProxyService.startPacSystem(); + mPacUrl = proxy.getPacFileUrl(); + mCurrentDelay = DELAY_1; + getAlarmManager().cancel(mPacRefreshIntent); + new Thread(this).start(); + } catch (RemoteException e) { + Log.e(TAG, "Unable to reach ProxyService - PAC will not be started", e); + } + } else { + try { + mProxyService.stopPacSystem(); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + } + + /** + * Does a post and reports back the status code. + * + * @throws IOException + */ + public static String get(String urlString) throws IOException { + URL url = new URL(urlString); + URLConnection urlConnection = url.openConnection(java.net.Proxy.NO_PROXY); + BufferedReader in = new BufferedReader(new InputStreamReader( + urlConnection.getInputStream())); + String inputLine; + String resp = ""; + while ((inputLine = in.readLine()) != null) { + resp = resp + inputLine + "\n"; + } + in.close(); + return resp; + } + + private static String toString(InputStream content) throws IOException { + StringBuffer buffer = new StringBuffer(); + String line; + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(content)); + + while ((line = bufferedReader.readLine()) != null) { + if (buffer.length() != 0) { + buffer.append('\n'); + } + buffer.append(line); + } + + return buffer.toString(); + } + + @Override + public void run() { + String file; + try { + file = get(mPacUrl); + } catch (IOException ioe) { + file = null; + } + if (file != null) { + if (!file.equals(mCurrentPac)) { + setCurrentProxyScript(file); + } + longSchedule(); + } else { + reschedule(); + } + } + + private int getNextDelay(int currentDelay) { + if (++currentDelay > DELAY_4) { + return DELAY_4; + } + return currentDelay; + } + + private void longSchedule() { + mCurrentDelay = DELAY_1; + setDownloadIn(DELAY_LONG); + } + + private void reschedule() { + mCurrentDelay = getNextDelay(mCurrentDelay); + setDownloadIn(mCurrentDelay); + } + + private String getPacChangeDelay() { + final ContentResolver cr = mContext.getContentResolver(); + + /** Check system properties for the default value then use secure settings value, if any. */ + String defaultDelay = SystemProperties.get( + "conn." + Settings.Global.PAC_CHANGE_DELAY, + DEFAULT_DELAYS); + String val = Settings.Global.getString(cr, Settings.Global.PAC_CHANGE_DELAY); + return (val == null) ? defaultDelay : val; + } + + private long getDownloadDelay(int delayIndex) { + String[] list = getPacChangeDelay().split(" "); + if (delayIndex < list.length) { + return Long.parseLong(list[delayIndex]); + } + return 0; + } + + private void setDownloadIn(int delayIndex) { + long delay = getDownloadDelay(delayIndex); + long timeTillTrigger = 1000 * delay + SystemClock.elapsedRealtime(); + getAlarmManager().set(AlarmManager.ELAPSED_REALTIME, timeTillTrigger, mPacRefreshIntent); + } + + private boolean setCurrentProxyScript(String script) { + try { + if (mProxyService.setPacFile(script) != NO_ERROR) { + Log.e(TAG, "Unable to parse proxy script."); + return false; + } + mCurrentPac = script; + } catch (RemoteException e) { + Log.e(TAG, "Unable to set PAC file", e); + } + return true; + } +} diff --git a/services/java/com/android/server/display/DisplayManagerService.java b/services/java/com/android/server/display/DisplayManagerService.java index 20db4cd..4b3463c 100644 --- a/services/java/com/android/server/display/DisplayManagerService.java +++ b/services/java/com/android/server/display/DisplayManagerService.java @@ -314,6 +314,9 @@ public final class DisplayManagerService extends IDisplayManager.Stub { * to the display information synchronously so that applications will immediately * observe the new state. * + * NOTE: This method must be the only entry point by which the window manager + * influences the logical configuration of displays. + * * @param displayId The logical display id. * @param info The new data to be stored. */ @@ -322,9 +325,7 @@ public final class DisplayManagerService extends IDisplayManager.Stub { synchronized (mSyncRoot) { LogicalDisplay display = mLogicalDisplays.get(displayId); if (display != null) { - mTempDisplayInfo.copyFrom(display.getDisplayInfoLocked()); - display.setDisplayInfoOverrideFromWindowManagerLocked(info); - if (!mTempDisplayInfo.equals(display.getDisplayInfoLocked())) { + if (display.setDisplayInfoOverrideFromWindowManagerLocked(info)) { sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED); scheduleTraversalLocked(false); } @@ -333,18 +334,6 @@ public final class DisplayManagerService extends IDisplayManager.Stub { } /** - * Sets the overscan insets for a particular display. - */ - public void setOverscan(int displayId, int left, int top, int right, int bottom) { - synchronized (mSyncRoot) { - LogicalDisplay display = mLogicalDisplays.get(displayId); - if (display != null) { - display.setOverscan(left, top, right, bottom); - } - } - } - - /** * Called by the window manager to perform traversals while holding a * surface flinger transaction. */ diff --git a/services/java/com/android/server/display/LogicalDisplay.java b/services/java/com/android/server/display/LogicalDisplay.java index b9839c2..7e357c0 100644 --- a/services/java/com/android/server/display/LogicalDisplay.java +++ b/services/java/com/android/server/display/LogicalDisplay.java @@ -128,32 +128,24 @@ final class LogicalDisplay { * * @param info The logical display information, may be null. */ - public void setDisplayInfoOverrideFromWindowManagerLocked(DisplayInfo info) { + public boolean setDisplayInfoOverrideFromWindowManagerLocked(DisplayInfo info) { if (info != null) { if (mOverrideDisplayInfo == null) { mOverrideDisplayInfo = new DisplayInfo(info); mInfo = null; - } else if (!mOverrideDisplayInfo.equals(info)) { + return true; + } + if (!mOverrideDisplayInfo.equals(info)) { mOverrideDisplayInfo.copyFrom(info); mInfo = null; + return true; } } else if (mOverrideDisplayInfo != null) { mOverrideDisplayInfo = null; mInfo = null; + return true; } - } - - public void setOverscan(int left, int top, int right, int bottom) { - mInfo.overscanLeft = left; - mInfo.overscanTop = top; - mInfo.overscanRight = right; - mInfo.overscanBottom = bottom; - if (mOverrideDisplayInfo != null) { - mOverrideDisplayInfo.overscanLeft = left; - mOverrideDisplayInfo.overscanTop = top; - mOverrideDisplayInfo.overscanRight = right; - mOverrideDisplayInfo.overscanBottom = bottom; - } + return false; } /** diff --git a/services/java/com/android/server/firewall/AndFilter.java b/services/java/com/android/server/firewall/AndFilter.java index 8894951..13551de 100644 --- a/services/java/com/android/server/firewall/AndFilter.java +++ b/services/java/com/android/server/firewall/AndFilter.java @@ -16,8 +16,8 @@ package com.android.server.firewall; +import android.content.ComponentName; import android.content.Intent; -import android.content.pm.ApplicationInfo; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -25,11 +25,11 @@ import java.io.IOException; class AndFilter extends FilterList { @Override - public boolean matches(IntentFirewall ifw, Intent intent, int callerUid, int callerPid, - String resolvedType, ApplicationInfo resolvedApp) { + public boolean matches(IntentFirewall ifw, ComponentName resolvedComponent, Intent intent, + int callerUid, int callerPid, String resolvedType, int receivingUid) { for (int i=0; i<children.size(); i++) { - if (!children.get(i).matches(ifw, intent, callerUid, callerPid, resolvedType, - resolvedApp)) { + if (!children.get(i).matches(ifw, resolvedComponent, intent, callerUid, callerPid, + resolvedType, receivingUid)) { return false; } } diff --git a/services/java/com/android/server/firewall/CategoryFilter.java b/services/java/com/android/server/firewall/CategoryFilter.java index e609b1e..246c096 100644 --- a/services/java/com/android/server/firewall/CategoryFilter.java +++ b/services/java/com/android/server/firewall/CategoryFilter.java @@ -16,8 +16,8 @@ package com.android.server.firewall; +import android.content.ComponentName; import android.content.Intent; -import android.content.pm.ApplicationInfo; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -34,8 +34,8 @@ class CategoryFilter implements Filter { } @Override - public boolean matches(IntentFirewall ifw, Intent intent, int callerUid, int callerPid, - String resolvedType, ApplicationInfo resolvedApp) { + public boolean matches(IntentFirewall ifw, ComponentName resolvedComponent, Intent intent, + int callerUid, int callerPid, String resolvedType, int receivingUid) { Set<String> categories = intent.getCategories(); if (categories == null) { return false; diff --git a/services/java/com/android/server/firewall/Filter.java b/services/java/com/android/server/firewall/Filter.java index 740cbe9..0a124f7 100644 --- a/services/java/com/android/server/firewall/Filter.java +++ b/services/java/com/android/server/firewall/Filter.java @@ -16,20 +16,21 @@ package com.android.server.firewall; +import android.content.ComponentName; import android.content.Intent; -import android.content.pm.ApplicationInfo; interface Filter { /** * Does the given intent + context info match this filter? * * @param ifw The IntentFirewall instance + * @param resolvedComponent The actual component that the intent was resolved to * @param intent The intent being started/bound/broadcast * @param callerUid The uid of the caller * @param callerPid The pid of the caller * @param resolvedType The resolved mime type of the intent - * @param resolvedApp The application that contains the resolved component that the intent is + * @param receivingUid The uid of the component receiving the intent */ - boolean matches(IntentFirewall ifw, Intent intent, int callerUid, int callerPid, - String resolvedType, ApplicationInfo resolvedApp); + boolean matches(IntentFirewall ifw, ComponentName resolvedComponent, Intent intent, + int callerUid, int callerPid, String resolvedType, int receivingUid); } diff --git a/services/java/com/android/server/firewall/IntentFirewall.java b/services/java/com/android/server/firewall/IntentFirewall.java index dfe89dd..aaa0b58 100644 --- a/services/java/com/android/server/firewall/IntentFirewall.java +++ b/services/java/com/android/server/firewall/IntentFirewall.java @@ -28,8 +28,10 @@ import android.os.FileObserver; import android.os.Handler; import android.os.Message; import android.os.RemoteException; +import android.util.ArrayMap; import android.util.Slog; import android.util.Xml; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.XmlUtils; import com.android.server.EventLogTags; import com.android.server.IntentResolver; @@ -41,6 +43,7 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -120,25 +123,43 @@ public class IntentFirewall { */ public boolean checkStartActivity(Intent intent, int callerUid, int callerPid, String resolvedType, ApplicationInfo resolvedApp) { - return checkIntent(mActivityResolver, TYPE_ACTIVITY, intent, callerUid, callerPid, - resolvedType, resolvedApp); + return checkIntent(mActivityResolver, intent.getComponent(), TYPE_ACTIVITY, intent, + callerUid, callerPid, resolvedType, resolvedApp.uid); } - public boolean checkService(Intent intent, int callerUid, int callerPid, String resolvedType, - ApplicationInfo resolvedApp) { - return checkIntent(mServiceResolver, TYPE_SERVICE, intent, callerUid, callerPid, - resolvedType, resolvedApp); + public boolean checkService(ComponentName resolvedService, Intent intent, int callerUid, + int callerPid, String resolvedType, ApplicationInfo resolvedApp) { + return checkIntent(mServiceResolver, resolvedService, TYPE_SERVICE, intent, callerUid, + callerPid, resolvedType, resolvedApp.uid); } - public boolean checkIntent(FirewallIntentResolver resolver, int intentType, Intent intent, - int callerUid, int callerPid, String resolvedType, ApplicationInfo resolvedApp) { - List<Rule> matchingRules = resolver.queryIntent(intent, resolvedType, false, 0); + public boolean checkBroadcast(Intent intent, int callerUid, int callerPid, + String resolvedType, int receivingUid) { + return checkIntent(mBroadcastResolver, intent.getComponent(), TYPE_BROADCAST, intent, + callerUid, callerPid, resolvedType, receivingUid); + } + + public boolean checkIntent(FirewallIntentResolver resolver, ComponentName resolvedComponent, + int intentType, Intent intent, int callerUid, int callerPid, String resolvedType, + int receivingUid) { boolean log = false; boolean block = false; - for (int i=0; i< matchingRules.size(); i++) { - Rule rule = matchingRules.get(i); - if (rule.matches(this, intent, callerUid, callerPid, resolvedType, resolvedApp)) { + // For the first pass, find all the rules that have at least one intent-filter or + // component-filter that matches this intent + List<Rule> candidateRules; + candidateRules = resolver.queryIntent(intent, resolvedType, false, 0); + if (candidateRules == null) { + candidateRules = new ArrayList<Rule>(); + } + resolver.queryByComponent(resolvedComponent, candidateRules); + + // For the second pass, try to match the potentially more specific conditions in each + // rule against the intent + for (int i=0; i<candidateRules.size(); i++) { + Rule rule = candidateRules.get(i); + if (rule.matches(this, resolvedComponent, intent, callerUid, callerPid, resolvedType, + receivingUid)) { block |= rule.getBlock(); log |= rule.getLog(); @@ -345,8 +366,11 @@ public class IntentFirewall { for (int ruleIndex=0; ruleIndex<rules.size(); ruleIndex++) { Rule rule = rules.get(ruleIndex); - for (int filterIndex=0; filterIndex<rule.getIntentFilterCount(); filterIndex++) { - resolver.addFilter(rule.getIntentFilter(filterIndex)); + for (int i=0; i<rule.getIntentFilterCount(); i++) { + resolver.addFilter(rule.getIntentFilter(i)); + } + for (int i=0; i<rule.getComponentFilterCount(); i++) { + resolver.addComponentFilter(rule.getComponentFilter(i), rule); } } } @@ -363,14 +387,35 @@ public class IntentFirewall { return factory.newFilter(parser); } + /** + * Represents a single activity/service/broadcast rule within one of the xml files. + * + * Rules are matched against an incoming intent in two phases. The goal of the first phase + * is to select a subset of rules that might match a given intent. + * + * For the first phase, we use a combination of intent filters (via an IntentResolver) + * and component filters to select which rules to check. If a rule has multiple intent or + * component filters, only a single filter must match for the rule to be passed on to the + * second phase. + * + * In the second phase, we check the specific conditions in each rule against the values in the + * intent. All top level conditions (but not filters) in the rule must match for the rule as a + * whole to match. + * + * If the rule matches, then we block or log the intent, as specified by the rule. If multiple + * rules match, we combine the block/log flags from any matching rule. + */ private static class Rule extends AndFilter { private static final String TAG_INTENT_FILTER = "intent-filter"; + private static final String TAG_COMPONENT_FILTER = "component-filter"; + private static final String ATTR_NAME = "name"; private static final String ATTR_BLOCK = "block"; private static final String ATTR_LOG = "log"; private final ArrayList<FirewallIntentFilter> mIntentFilters = new ArrayList<FirewallIntentFilter>(1); + private final ArrayList<ComponentName> mComponentFilters = new ArrayList<ComponentName>(0); private boolean block; private boolean log; @@ -385,10 +430,25 @@ public class IntentFirewall { @Override protected void readChild(XmlPullParser parser) throws IOException, XmlPullParserException { - if (parser.getName().equals(TAG_INTENT_FILTER)) { + String currentTag = parser.getName(); + + if (currentTag.equals(TAG_INTENT_FILTER)) { FirewallIntentFilter intentFilter = new FirewallIntentFilter(this); intentFilter.readFromXml(parser); mIntentFilters.add(intentFilter); + } else if (currentTag.equals(TAG_COMPONENT_FILTER)) { + String componentStr = parser.getAttributeValue(null, ATTR_NAME); + if (componentStr == null) { + throw new XmlPullParserException("Component name must be specified.", + parser, null); + } + + ComponentName componentName = ComponentName.unflattenFromString(componentStr); + if (componentName == null) { + throw new XmlPullParserException("Invalid component name: " + componentStr); + } + + mComponentFilters.add(componentName); } else { super.readChild(parser); } @@ -402,6 +462,13 @@ public class IntentFirewall { return mIntentFilters.get(index); } + public int getComponentFilterCount() { + return mComponentFilters.size(); + } + + public ComponentName getComponentFilter(int index) { + return mComponentFilters.get(index); + } public boolean getBlock() { return block; } @@ -446,6 +513,22 @@ public class IntentFirewall { // there's no need to sort the results return; } + + public void queryByComponent(ComponentName componentName, List<Rule> candidateRules) { + Rule[] rules = mRulesByComponent.get(componentName); + if (rules != null) { + candidateRules.addAll(Arrays.asList(rules)); + } + } + + public void addComponentFilter(ComponentName componentName, Rule rule) { + Rule[] rules = mRulesByComponent.get(componentName); + rules = ArrayUtils.appendElement(Rule.class, rules, rule); + mRulesByComponent.put(componentName, rules); + } + + private final ArrayMap<ComponentName, Rule[]> mRulesByComponent = + new ArrayMap<ComponentName, Rule[]>(0); } final Handler mHandler = new Handler() { diff --git a/services/java/com/android/server/firewall/NotFilter.java b/services/java/com/android/server/firewall/NotFilter.java index 327eb59..09bf629 100644 --- a/services/java/com/android/server/firewall/NotFilter.java +++ b/services/java/com/android/server/firewall/NotFilter.java @@ -16,8 +16,8 @@ package com.android.server.firewall; +import android.content.ComponentName; import android.content.Intent; -import android.content.pm.ApplicationInfo; import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -32,9 +32,10 @@ class NotFilter implements Filter { } @Override - public boolean matches(IntentFirewall ifw, Intent intent, int callerUid, int callerPid, - String resolvedType, ApplicationInfo resolvedApp) { - return !mChild.matches(ifw, intent, callerUid, callerPid, resolvedType, resolvedApp); + public boolean matches(IntentFirewall ifw, ComponentName resolvedComponent, Intent intent, + int callerUid, int callerPid, String resolvedType, int receivingUid) { + return !mChild.matches(ifw, resolvedComponent, intent, callerUid, callerPid, resolvedType, + receivingUid); } public static final FilterFactory FACTORY = new FilterFactory("not") { diff --git a/services/java/com/android/server/firewall/OrFilter.java b/services/java/com/android/server/firewall/OrFilter.java index 23c0fe0..f6a6f22 100644 --- a/services/java/com/android/server/firewall/OrFilter.java +++ b/services/java/com/android/server/firewall/OrFilter.java @@ -16,8 +16,8 @@ package com.android.server.firewall; +import android.content.ComponentName; import android.content.Intent; -import android.content.pm.ApplicationInfo; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -25,11 +25,11 @@ import java.io.IOException; class OrFilter extends FilterList { @Override - public boolean matches(IntentFirewall ifw, Intent intent, int callerUid, int callerPid, - String resolvedType, ApplicationInfo resolvedApp) { + public boolean matches(IntentFirewall ifw, ComponentName resolvedComponent, Intent intent, + int callerUid, int callerPid, String resolvedType, int receivingUid) { for (int i=0; i<children.size(); i++) { - if (children.get(i).matches(ifw, intent, callerUid, callerPid, resolvedType, - resolvedApp)) { + if (children.get(i).matches(ifw, resolvedComponent, intent, callerUid, callerPid, + resolvedType, receivingUid)) { return true; } } diff --git a/services/java/com/android/server/firewall/PortFilter.java b/services/java/com/android/server/firewall/PortFilter.java index ac4504f..84ace55 100644 --- a/services/java/com/android/server/firewall/PortFilter.java +++ b/services/java/com/android/server/firewall/PortFilter.java @@ -16,8 +16,8 @@ package com.android.server.firewall; +import android.content.ComponentName; import android.content.Intent; -import android.content.pm.ApplicationInfo; import android.net.Uri; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -41,8 +41,8 @@ class PortFilter implements Filter { } @Override - public boolean matches(IntentFirewall ifw, Intent intent, int callerUid, int callerPid, - String resolvedType, ApplicationInfo resolvedApp) { + public boolean matches(IntentFirewall ifw, ComponentName resolvedComponent, Intent intent, + int callerUid, int callerPid, String resolvedType, int receivingUid) { int port = -1; Uri uri = intent.getData(); if (uri != null) { diff --git a/services/java/com/android/server/firewall/SenderFilter.java b/services/java/com/android/server/firewall/SenderFilter.java index 3d7b450..c0eee69 100644 --- a/services/java/com/android/server/firewall/SenderFilter.java +++ b/services/java/com/android/server/firewall/SenderFilter.java @@ -17,6 +17,7 @@ package com.android.server.firewall; import android.app.AppGlobals; +import android.content.ComponentName; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; @@ -77,38 +78,38 @@ class SenderFilter { private static final Filter SIGNATURE = new Filter() { @Override - public boolean matches(IntentFirewall ifw, Intent intent, int callerUid, int callerPid, - String resolvedType, ApplicationInfo resolvedApp) { - return ifw.signaturesMatch(callerUid, resolvedApp.uid); + public boolean matches(IntentFirewall ifw, ComponentName resolvedComponent, Intent intent, + int callerUid, int callerPid, String resolvedType, int receivingUid) { + return ifw.signaturesMatch(callerUid, receivingUid); } }; private static final Filter SYSTEM = new Filter() { @Override - public boolean matches(IntentFirewall ifw, Intent intent, int callerUid, int callerPid, - String resolvedType, ApplicationInfo resolvedApp) { + public boolean matches(IntentFirewall ifw, ComponentName resolvedComponent, Intent intent, + int callerUid, int callerPid, String resolvedType, int receivingUid) { return isPrivilegedApp(callerUid, callerPid); } }; private static final Filter SYSTEM_OR_SIGNATURE = new Filter() { @Override - public boolean matches(IntentFirewall ifw, Intent intent, int callerUid, int callerPid, - String resolvedType, ApplicationInfo resolvedApp) { + public boolean matches(IntentFirewall ifw, ComponentName resolvedComponent, Intent intent, + int callerUid, int callerPid, String resolvedType, int receivingUid) { return isPrivilegedApp(callerUid, callerPid) || - ifw.signaturesMatch(callerUid, resolvedApp.uid); + ifw.signaturesMatch(callerUid, receivingUid); } }; private static final Filter USER_ID = new Filter() { @Override - public boolean matches(IntentFirewall ifw, Intent intent, int callerUid, int callerPid, - String resolvedType, ApplicationInfo resolvedApp) { + public boolean matches(IntentFirewall ifw, ComponentName resolvedComponent, Intent intent, + int callerUid, int callerPid, String resolvedType, int receivingUid) { // This checks whether the caller is either the system process, or has the same user id // I.e. the same app, or an app that uses the same shared user id. // This is the same set of applications that would be able to access the component if // it wasn't exported. - return ifw.checkComponentPermission(null, callerPid, callerUid, resolvedApp.uid, false); + return ifw.checkComponentPermission(null, callerPid, callerUid, receivingUid, false); } }; } diff --git a/services/java/com/android/server/firewall/SenderPermissionFilter.java b/services/java/com/android/server/firewall/SenderPermissionFilter.java index a88ceaf..caa65f3 100644 --- a/services/java/com/android/server/firewall/SenderPermissionFilter.java +++ b/services/java/com/android/server/firewall/SenderPermissionFilter.java @@ -16,8 +16,8 @@ package com.android.server.firewall; +import android.content.ComponentName; import android.content.Intent; -import android.content.pm.ApplicationInfo; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -33,12 +33,12 @@ class SenderPermissionFilter implements Filter { } @Override - public boolean matches(IntentFirewall ifw, Intent intent, int callerUid, int callerPid, - String resolvedType, ApplicationInfo resolvedApp) { + public boolean matches(IntentFirewall ifw, ComponentName resolvedComponent, Intent intent, + int callerUid, int callerPid, String resolvedType, int receivingUid) { // We assume the component is exported here. If the component is not exported, then // ActivityManager would only resolve to this component for callers from the same uid. // In this case, it doesn't matter whether the component is exported or not. - return ifw.checkComponentPermission(mPermission, callerPid, callerUid, resolvedApp.uid, + return ifw.checkComponentPermission(mPermission, callerPid, callerUid, receivingUid, true); } diff --git a/services/java/com/android/server/firewall/StringFilter.java b/services/java/com/android/server/firewall/StringFilter.java index 3fa09f3..28e99b3 100644 --- a/services/java/com/android/server/firewall/StringFilter.java +++ b/services/java/com/android/server/firewall/StringFilter.java @@ -18,7 +18,6 @@ package com.android.server.firewall; import android.content.ComponentName; import android.content.Intent; -import android.content.pm.ApplicationInfo; import android.net.Uri; import android.os.PatternMatcher; import org.xmlpull.v1.XmlPullParser; @@ -119,9 +118,9 @@ abstract class StringFilter implements Filter { protected abstract boolean matchesValue(String value); @Override - public boolean matches(IntentFirewall ifw, Intent intent, int callerUid, int callerPid, - String resolvedType, ApplicationInfo resolvedApp) { - String value = mValueProvider.getValue(intent, resolvedType, resolvedApp); + public boolean matches(IntentFirewall ifw, ComponentName resolvedComponent, Intent intent, + int callerUid, int callerPid, String resolvedType, int receivingUid) { + String value = mValueProvider.getValue(resolvedComponent, intent, resolvedType); return matchesValue(value); } @@ -135,8 +134,8 @@ abstract class StringFilter implements Filter { return StringFilter.readFromXml(this, parser); } - public abstract String getValue(Intent intent, String resolvedType, - ApplicationInfo resolvedApp); + public abstract String getValue(ComponentName resolvedComponent, Intent intent, + String resolvedType); } private static class EqualsFilter extends StringFilter { @@ -230,10 +229,10 @@ abstract class StringFilter implements Filter { public static final ValueProvider COMPONENT = new ValueProvider("component") { @Override - public String getValue(Intent intent, String resolvedType, ApplicationInfo resolvedApp) { - ComponentName cn = intent.getComponent(); - if (cn != null) { - return cn.flattenToString(); + public String getValue(ComponentName resolvedComponent, Intent intent, + String resolvedType) { + if (resolvedComponent != null) { + return resolvedComponent.flattenToString(); } return null; } @@ -241,10 +240,10 @@ abstract class StringFilter implements Filter { public static final ValueProvider COMPONENT_NAME = new ValueProvider("component-name") { @Override - public String getValue(Intent intent, String resolvedType, ApplicationInfo resolvedApp) { - ComponentName cn = intent.getComponent(); - if (cn != null) { - return cn.getClassName(); + public String getValue(ComponentName resolvedComponent, Intent intent, + String resolvedType) { + if (resolvedComponent != null) { + return resolvedComponent.getClassName(); } return null; } @@ -252,10 +251,10 @@ abstract class StringFilter implements Filter { public static final ValueProvider COMPONENT_PACKAGE = new ValueProvider("component-package") { @Override - public String getValue(Intent intent, String resolvedType, ApplicationInfo resolvedApp) { - ComponentName cn = intent.getComponent(); - if (cn != null) { - return cn.getPackageName(); + public String getValue(ComponentName resolvedComponent, Intent intent, + String resolvedType) { + if (resolvedComponent != null) { + return resolvedComponent.getPackageName(); } return null; } @@ -263,14 +262,16 @@ abstract class StringFilter implements Filter { public static final FilterFactory ACTION = new ValueProvider("action") { @Override - public String getValue(Intent intent, String resolvedType, ApplicationInfo resolvedApp) { + public String getValue(ComponentName resolvedComponent, Intent intent, + String resolvedType) { return intent.getAction(); } }; public static final ValueProvider DATA = new ValueProvider("data") { @Override - public String getValue(Intent intent, String resolvedType, ApplicationInfo resolvedApp) { + public String getValue(ComponentName resolvedComponent, Intent intent, + String resolvedType) { Uri data = intent.getData(); if (data != null) { return data.toString(); @@ -281,14 +282,16 @@ abstract class StringFilter implements Filter { public static final ValueProvider MIME_TYPE = new ValueProvider("mime-type") { @Override - public String getValue(Intent intent, String resolvedType, ApplicationInfo resolvedApp) { + public String getValue(ComponentName resolvedComponent, Intent intent, + String resolvedType) { return resolvedType; } }; public static final ValueProvider SCHEME = new ValueProvider("scheme") { @Override - public String getValue(Intent intent, String resolvedType, ApplicationInfo resolvedApp) { + public String getValue(ComponentName resolvedComponent, Intent intent, + String resolvedType) { Uri data = intent.getData(); if (data != null) { return data.getScheme(); @@ -299,7 +302,8 @@ abstract class StringFilter implements Filter { public static final ValueProvider SSP = new ValueProvider("scheme-specific-part") { @Override - public String getValue(Intent intent, String resolvedType, ApplicationInfo resolvedApp) { + public String getValue(ComponentName resolvedComponent, Intent intent, + String resolvedType) { Uri data = intent.getData(); if (data != null) { return data.getSchemeSpecificPart(); @@ -310,7 +314,8 @@ abstract class StringFilter implements Filter { public static final ValueProvider HOST = new ValueProvider("host") { @Override - public String getValue(Intent intent, String resolvedType, ApplicationInfo resolvedApp) { + public String getValue(ComponentName resolvedComponent, Intent intent, + String resolvedType) { Uri data = intent.getData(); if (data != null) { return data.getHost(); @@ -321,7 +326,8 @@ abstract class StringFilter implements Filter { public static final ValueProvider PATH = new ValueProvider("path") { @Override - public String getValue(Intent intent, String resolvedType, ApplicationInfo resolvedApp) { + public String getValue(ComponentName resolvedComponent, Intent intent, + String resolvedType) { Uri data = intent.getData(); if (data != null) { return data.getPath(); diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java index 27177f9..364b854 100644 --- a/services/java/com/android/server/wm/WindowManagerService.java +++ b/services/java/com/android/server/wm/WindowManagerService.java @@ -6961,12 +6961,7 @@ public class WindowManagerService extends IWindowManager.Stub synchronized(mWindowMap) { mIsTouchDevice = mContext.getPackageManager().hasSystemFeature( PackageManager.FEATURE_TOUCHSCREEN); - - final DisplayContent displayContent = getDefaultDisplayContentLocked(); - mPolicy.setInitialDisplaySize(displayContent.getDisplay(), - displayContent.mInitialDisplayWidth, - displayContent.mInitialDisplayHeight, - displayContent.mInitialDisplayDensity); + configureDisplayPolicyLocked(getDefaultDisplayContentLocked()); } try { @@ -7793,11 +7788,7 @@ public class WindowManagerService extends IWindowManager.Stub // displayContent must not be null private void reconfigureDisplayLocked(DisplayContent displayContent) { // TODO: Multidisplay: for now only use with default display. - mPolicy.setInitialDisplaySize(displayContent.getDisplay(), - displayContent.mBaseDisplayWidth, - displayContent.mBaseDisplayHeight, - displayContent.mBaseDisplayDensity); - + configureDisplayPolicyLocked(displayContent); displayContent.layoutNeeded = true; boolean configChanged = updateOrientationFromAppTokensLocked(false); @@ -7818,6 +7809,18 @@ public class WindowManagerService extends IWindowManager.Stub performLayoutAndPlaceSurfacesLocked(); } + private void configureDisplayPolicyLocked(DisplayContent displayContent) { + mPolicy.setInitialDisplaySize(displayContent.getDisplay(), + displayContent.mBaseDisplayWidth, + displayContent.mBaseDisplayHeight, + displayContent.mBaseDisplayDensity); + + DisplayInfo displayInfo = displayContent.getDisplayInfo(); + mPolicy.setDisplayOverscan(displayContent.getDisplay(), + displayInfo.overscanLeft, displayInfo.overscanTop, + displayInfo.overscanRight, displayInfo.overscanBottom); + } + @Override public void setOverscan(int displayId, int left, int top, int right, int bottom) { if (mContext.checkCallingOrSelfPermission( @@ -7829,23 +7832,27 @@ public class WindowManagerService extends IWindowManager.Stub synchronized(mWindowMap) { DisplayContent displayContent = getDisplayContentLocked(displayId); if (displayContent != null) { - mDisplayManagerService.setOverscan(displayId, left, top, right, bottom); - final DisplayInfo displayInfo = displayContent.getDisplayInfo(); - synchronized(displayContent.mDisplaySizeLock) { - displayInfo.overscanLeft = left; - displayInfo.overscanTop = top; - displayInfo.overscanRight = right; - displayInfo.overscanBottom = bottom; - } - mPolicy.setDisplayOverscan(displayContent.getDisplay(), left, top, right, bottom); - displayContent.layoutNeeded = true; - mDisplaySettings.setOverscanLocked(displayInfo.name, left, top, right, bottom); - mDisplaySettings.writeSettingsLocked(); - performLayoutAndPlaceSurfacesLocked(); + setOverscanLocked(displayContent, left, top, right, bottom); } } } + private void setOverscanLocked(DisplayContent displayContent, + int left, int top, int right, int bottom) { + final DisplayInfo displayInfo = displayContent.getDisplayInfo(); + synchronized (displayContent.mDisplaySizeLock) { + displayInfo.overscanLeft = left; + displayInfo.overscanTop = top; + displayInfo.overscanRight = right; + displayInfo.overscanBottom = bottom; + } + + mDisplaySettings.setOverscanLocked(displayInfo.name, left, top, right, bottom); + mDisplaySettings.writeSettingsLocked(); + + reconfigureDisplayLocked(displayContent); + } + // ------------------------------------------------------------- // Internals // ------------------------------------------------------------- @@ -10719,17 +10726,19 @@ public class WindowManagerService extends IWindowManager.Stub DisplayContent displayContent = new DisplayContent(display, this); final int displayId = display.getDisplayId(); mDisplayContents.put(displayId, displayContent); + + DisplayInfo displayInfo = displayContent.getDisplayInfo(); final Rect rect = new Rect(); - DisplayInfo info = displayContent.getDisplayInfo(); - mDisplaySettings.getOverscanLocked(info.name, rect); - info.overscanLeft = rect.left; - info.overscanTop = rect.top; - info.overscanRight = rect.right; - info.overscanBottom = rect.bottom; - mDisplayManagerService.setOverscan(display.getDisplayId(), rect.left, rect.top, - rect.right, rect.bottom); - mPolicy.setDisplayOverscan(displayContent.getDisplay(), rect.left, rect.top, - rect.right, rect.bottom); + mDisplaySettings.getOverscanLocked(displayInfo.name, rect); + synchronized (displayContent.mDisplaySizeLock) { + displayInfo.overscanLeft = rect.left; + displayInfo.overscanTop = rect.top; + displayInfo.overscanRight = rect.right; + displayInfo.overscanBottom = rect.bottom; + mDisplayManagerService.setDisplayInfoOverrideFromWindowManager( + displayId, displayInfo); + } + configureDisplayPolicyLocked(displayContent); // TODO: Create an input channel for each display with touch capability. if (displayId == Display.DEFAULT_DISPLAY) { diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java index d6abbaa..3eec7a2 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java @@ -63,7 +63,7 @@ public class BridgeIInputMethodManager implements IInputMethodManager { } @Override - public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(InputMethodInfo arg0, + public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String arg0, boolean arg1) throws RemoteException { // TODO Auto-generated method stub return null; diff --git a/wifi/java/android/net/wifi/WifiConfigStore.java b/wifi/java/android/net/wifi/WifiConfigStore.java index 9418de1..ad700aa 100644 --- a/wifi/java/android/net/wifi/WifiConfigStore.java +++ b/wifi/java/android/net/wifi/WifiConfigStore.java @@ -141,6 +141,7 @@ class WifiConfigStore { private static final String PROXY_SETTINGS_KEY = "proxySettings"; private static final String PROXY_HOST_KEY = "proxyHost"; private static final String PROXY_PORT_KEY = "proxyPort"; + private static final String PROXY_PAC_FILE = "proxyPac"; private static final String EXCLUSION_LIST_KEY = "exclusionList"; private static final String EOS = "eos"; @@ -771,6 +772,14 @@ class WifiConfigStore { out.writeUTF(exclusionList); writeToFile = true; break; + case PAC: + ProxyProperties proxyPacProperties = linkProperties.getHttpProxy(); + out.writeUTF(PROXY_SETTINGS_KEY); + out.writeUTF(config.proxySettings.toString()); + out.writeUTF(PROXY_PAC_FILE); + out.writeUTF(proxyPacProperties.getPacFileUrl()); + writeToFile = true; + break; case NONE: out.writeUTF(PROXY_SETTINGS_KEY); out.writeUTF(config.proxySettings.toString()); @@ -838,6 +847,7 @@ class WifiConfigStore { ProxySettings proxySettings = ProxySettings.NONE; LinkProperties linkProperties = new LinkProperties(); String proxyHost = null; + String pacFileUrl = null; int proxyPort = -1; String exclusionList = null; String key; @@ -879,6 +889,8 @@ class WifiConfigStore { proxyHost = in.readUTF(); } else if (key.equals(PROXY_PORT_KEY)) { proxyPort = in.readInt(); + } else if (key.equals(PROXY_PAC_FILE)) { + pacFileUrl = in.readUTF(); } else if (key.equals(EXCLUSION_LIST_KEY)) { exclusionList = in.readUTF(); } else if (key.equals(EOS)) { @@ -920,6 +932,12 @@ class WifiConfigStore { new ProxyProperties(proxyHost, proxyPort, exclusionList); linkProperties.setHttpProxy(proxyProperties); break; + case PAC: + config.proxySettings = proxySettings; + ProxyProperties proxyPacProperties = + new ProxyProperties(pacFileUrl); + linkProperties.setHttpProxy(proxyPacProperties); + break; case NONE: config.proxySettings = proxySettings; break; @@ -1247,6 +1265,7 @@ class WifiConfigStore { switch (newConfig.proxySettings) { case STATIC: + case PAC: ProxyProperties newHttpProxy = newConfig.linkProperties.getHttpProxy(); ProxyProperties currentHttpProxy = currentConfig.linkProperties.getHttpProxy(); diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java index bd8f0eb..de377ee 100644 --- a/wifi/java/android/net/wifi/WifiConfiguration.java +++ b/wifi/java/android/net/wifi/WifiConfiguration.java @@ -312,7 +312,10 @@ public class WifiConfiguration implements Parcelable { STATIC, /* no proxy details are assigned, this is used to indicate * that any existing proxy settings should be retained */ - UNASSIGNED + UNASSIGNED, + /* Use a Pac based proxy. + */ + PAC } /** * @hide |