Merge changes I4e32a588,I202c5653

* changes:
  Make MediaHTTPConnection thread safe.
  Revert "MediaHTTPConnection: move connection states into an inner class"
This commit is contained in:
Tobias Thierer
2019-04-06 14:21:32 +00:00
committed by Gerrit Code Review
2 changed files with 123 additions and 130 deletions

View File

@ -24,6 +24,7 @@ import android.os.IBinder;
import android.os.StrictMode; import android.os.StrictMode;
import android.util.Log; import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -37,7 +38,6 @@ import java.net.URL;
import java.net.UnknownServiceException; import java.net.UnknownServiceException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
/** @hide */ /** @hide */
public class MediaHTTPConnection extends IMediaHTTPConnection.Stub { public class MediaHTTPConnection extends IMediaHTTPConnection.Stub {
@ -47,23 +47,42 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub {
// connection timeout - 30 sec // connection timeout - 30 sec
private static final int CONNECT_TIMEOUT_MS = 30 * 1000; private static final int CONNECT_TIMEOUT_MS = 30 * 1000;
@GuardedBy("this")
@UnsupportedAppUsage
private long mCurrentOffset = -1;
@GuardedBy("this")
@UnsupportedAppUsage
private URL mURL = null;
@GuardedBy("this")
@UnsupportedAppUsage
private Map<String, String> mHeaders = null;
// volatile so that disconnect() can be called without acquiring a lock.
// All other access is @GuardedBy("this").
@UnsupportedAppUsage
private volatile HttpURLConnection mConnection = null;
@GuardedBy("this")
@UnsupportedAppUsage
private long mTotalSize = -1;
@GuardedBy("this")
private InputStream mInputStream = null;
@GuardedBy("this")
@UnsupportedAppUsage
private boolean mAllowCrossDomainRedirect = true;
@GuardedBy("this")
@UnsupportedAppUsage
private boolean mAllowCrossProtocolRedirect = true;
// from com.squareup.okhttp.internal.http // from com.squareup.okhttp.internal.http
private final static int HTTP_TEMP_REDIRECT = 307; private final static int HTTP_TEMP_REDIRECT = 307;
private final static int MAX_REDIRECTS = 20; private final static int MAX_REDIRECTS = 20;
class ConnectionState {
public HttpURLConnection mConnection = null;
public InputStream mInputStream = null;
public long mCurrentOffset = -1;
public Map<String, String> mHeaders = null;
public URL mURL = null;
public long mTotalSize = -1;
public boolean mAllowCrossDomainRedirect = true;
public boolean mAllowCrossProtocolRedirect = true;
}
private final AtomicReference<ConnectionState> mConnectionStateHolder =
new AtomicReference<ConnectionState>();
@UnsupportedAppUsage @UnsupportedAppUsage
public MediaHTTPConnection() { public MediaHTTPConnection() {
CookieHandler cookieHandler = CookieHandler.getDefault(); CookieHandler cookieHandler = CookieHandler.getDefault();
@ -76,34 +95,24 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub {
@Override @Override
@UnsupportedAppUsage @UnsupportedAppUsage
public IBinder connect(String uri, String headers) { public synchronized IBinder connect(String uri, String headers) {
if (VERBOSE) { if (VERBOSE) {
Log.d(TAG, "connect: uri=" + uri + ", headers=" + headers); Log.d(TAG, "connect: uri=" + uri + ", headers=" + headers);
} }
ConnectionState connectionState = mConnectionStateHolder.get();
synchronized (this) {
if (connectionState == null) {
connectionState = new ConnectionState();
mConnectionStateHolder.set(connectionState);
}
}
try { try {
disconnect(); disconnect();
connectionState.mAllowCrossDomainRedirect = true; mAllowCrossDomainRedirect = true;
connectionState.mURL = new URL(uri); mURL = new URL(uri);
connectionState.mHeaders = convertHeaderStringToMap(headers, connectionState); mHeaders = convertHeaderStringToMap(headers);
} catch (MalformedURLException e) { } catch (MalformedURLException e) {
return null; return null;
} finally {
mConnectionStateHolder.set(connectionState);
} }
return native_getIMemory(); return native_getIMemory();
} }
private boolean parseBoolean(String val) { private static boolean parseBoolean(String val) {
try { try {
return Long.parseLong(val) != 0; return Long.parseLong(val) != 0;
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
@ -113,21 +122,18 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub {
} }
/* returns true iff header is internal */ /* returns true iff header is internal */
private boolean filterOutInternalHeaders( private synchronized boolean filterOutInternalHeaders(String key, String val) {
String key, String val, ConnectionState connectionState) {
if ("android-allow-cross-domain-redirect".equalsIgnoreCase(key)) { if ("android-allow-cross-domain-redirect".equalsIgnoreCase(key)) {
connectionState.mAllowCrossDomainRedirect = parseBoolean(val); mAllowCrossDomainRedirect = parseBoolean(val);
// cross-protocol redirects are also controlled by this flag // cross-protocol redirects are also controlled by this flag
connectionState.mAllowCrossProtocolRedirect = mAllowCrossProtocolRedirect = mAllowCrossDomainRedirect;
connectionState.mAllowCrossDomainRedirect;
} else { } else {
return false; return false;
} }
return true; return true;
} }
private Map<String, String> convertHeaderStringToMap(String headers, private synchronized Map<String, String> convertHeaderStringToMap(String headers) {
ConnectionState connectionState) {
HashMap<String, String> map = new HashMap<String, String>(); HashMap<String, String> map = new HashMap<String, String>();
String[] pairs = headers.split("\r\n"); String[] pairs = headers.split("\r\n");
@ -137,7 +143,7 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub {
String key = pair.substring(0, colonPos); String key = pair.substring(0, colonPos);
String val = pair.substring(colonPos + 1); String val = pair.substring(colonPos + 1);
if (!filterOutInternalHeaders(key, val, connectionState)) { if (!filterOutInternalHeaders(key, val)) {
map.put(key, val); map.put(key, val);
} }
} }
@ -149,28 +155,36 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub {
@Override @Override
@UnsupportedAppUsage @UnsupportedAppUsage
public void disconnect() { public void disconnect() {
ConnectionState connectionState = mConnectionStateHolder.getAndSet(null); HttpURLConnection connectionToDisconnect = mConnection;
if (connectionState != null) { // Call disconnect() before blocking for the lock in order to ensure that any
teardownConnection(connectionState); // other thread that is blocked in readAt() will return quickly.
connectionState.mHeaders = null; if (connectionToDisconnect != null) {
connectionState.mURL = null; connectionToDisconnect.disconnect();
}
synchronized (this) {
// It's unlikely but possible that while we were waiting to acquire the lock, another
// thread concurrently started a new connection; if so, we're disconnecting that one
// here, too.
teardownConnection();
mHeaders = null;
mURL = null;
} }
} }
private void teardownConnection(ConnectionState connectionState) { private synchronized void teardownConnection() {
if (connectionState.mConnection != null) { if (mConnection != null) {
if (connectionState.mInputStream != null) { if (mInputStream != null) {
try { try {
connectionState.mInputStream.close(); mInputStream.close();
} catch (IOException e) { } catch (IOException e) {
} }
connectionState.mInputStream = null; mInputStream = null;
} }
connectionState.mConnection.disconnect(); mConnection.disconnect();
connectionState.mConnection = null; mConnection = null;
connectionState.mCurrentOffset = -1; mCurrentOffset = -1;
} }
} }
@ -197,44 +211,42 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub {
return false; return false;
} }
private void seekTo(long offset, ConnectionState connectionState) throws IOException { private synchronized void seekTo(long offset) throws IOException {
teardownConnection(connectionState); teardownConnection();
try { try {
int response; int response;
int redirectCount = 0; int redirectCount = 0;
URL url = connectionState.mURL; URL url = mURL;
// do not use any proxy for localhost (127.0.0.1) // do not use any proxy for localhost (127.0.0.1)
boolean noProxy = isLocalHost(url); boolean noProxy = isLocalHost(url);
while (true) { while (true) {
if (noProxy) { if (noProxy) {
connectionState.mConnection = mConnection = (HttpURLConnection)url.openConnection(Proxy.NO_PROXY);
(HttpURLConnection) url.openConnection(Proxy.NO_PROXY);
} else { } else {
connectionState.mConnection = (HttpURLConnection) url.openConnection(); mConnection = (HttpURLConnection)url.openConnection();
} }
connectionState.mConnection.setConnectTimeout(CONNECT_TIMEOUT_MS); mConnection.setConnectTimeout(CONNECT_TIMEOUT_MS);
// handle redirects ourselves if we do not allow cross-domain redirect // handle redirects ourselves if we do not allow cross-domain redirect
connectionState.mConnection.setInstanceFollowRedirects( mConnection.setInstanceFollowRedirects(mAllowCrossDomainRedirect);
connectionState.mAllowCrossDomainRedirect);
if (connectionState.mHeaders != null) { if (mHeaders != null) {
for (Map.Entry<String, String> entry : connectionState.mHeaders.entrySet()) { for (Map.Entry<String, String> entry : mHeaders.entrySet()) {
connectionState.mConnection.setRequestProperty( mConnection.setRequestProperty(
entry.getKey(), entry.getValue()); entry.getKey(), entry.getValue());
} }
} }
if (offset > 0) { if (offset > 0) {
connectionState.mConnection.setRequestProperty( mConnection.setRequestProperty(
"Range", "bytes=" + offset + "-"); "Range", "bytes=" + offset + "-");
} }
response = connectionState.mConnection.getResponseCode(); response = mConnection.getResponseCode();
if (response != HttpURLConnection.HTTP_MULT_CHOICE && if (response != HttpURLConnection.HTTP_MULT_CHOICE &&
response != HttpURLConnection.HTTP_MOVED_PERM && response != HttpURLConnection.HTTP_MOVED_PERM &&
response != HttpURLConnection.HTTP_MOVED_TEMP && response != HttpURLConnection.HTTP_MOVED_TEMP &&
@ -248,7 +260,7 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub {
throw new NoRouteToHostException("Too many redirects: " + redirectCount); throw new NoRouteToHostException("Too many redirects: " + redirectCount);
} }
String method = connectionState.mConnection.getRequestMethod(); String method = mConnection.getRequestMethod();
if (response == HTTP_TEMP_REDIRECT && if (response == HTTP_TEMP_REDIRECT &&
!method.equals("GET") && !method.equals("HEAD")) { !method.equals("GET") && !method.equals("HEAD")) {
// "If the 307 status code is received in response to a // "If the 307 status code is received in response to a
@ -256,35 +268,34 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub {
// automatically redirect the request" // automatically redirect the request"
throw new NoRouteToHostException("Invalid redirect"); throw new NoRouteToHostException("Invalid redirect");
} }
String location = connectionState.mConnection.getHeaderField("Location"); String location = mConnection.getHeaderField("Location");
if (location == null) { if (location == null) {
throw new NoRouteToHostException("Invalid redirect"); throw new NoRouteToHostException("Invalid redirect");
} }
url = new URL(connectionState.mURL /* TRICKY: don't use url! */, location); url = new URL(mURL /* TRICKY: don't use url! */, location);
if (!url.getProtocol().equals("https") && if (!url.getProtocol().equals("https") &&
!url.getProtocol().equals("http")) { !url.getProtocol().equals("http")) {
throw new NoRouteToHostException("Unsupported protocol redirect"); throw new NoRouteToHostException("Unsupported protocol redirect");
} }
boolean sameProtocol = boolean sameProtocol = mURL.getProtocol().equals(url.getProtocol());
connectionState.mURL.getProtocol().equals(url.getProtocol()); if (!mAllowCrossProtocolRedirect && !sameProtocol) {
if (!connectionState.mAllowCrossProtocolRedirect && !sameProtocol) {
throw new NoRouteToHostException("Cross-protocol redirects are disallowed"); throw new NoRouteToHostException("Cross-protocol redirects are disallowed");
} }
boolean sameHost = connectionState.mURL.getHost().equals(url.getHost()); boolean sameHost = mURL.getHost().equals(url.getHost());
if (!connectionState.mAllowCrossDomainRedirect && !sameHost) { if (!mAllowCrossDomainRedirect && !sameHost) {
throw new NoRouteToHostException("Cross-domain redirects are disallowed"); throw new NoRouteToHostException("Cross-domain redirects are disallowed");
} }
if (response != HTTP_TEMP_REDIRECT) { if (response != HTTP_TEMP_REDIRECT) {
// update effective URL, unless it is a Temporary Redirect // update effective URL, unless it is a Temporary Redirect
connectionState.mURL = url; mURL = url;
} }
} }
if (connectionState.mAllowCrossDomainRedirect) { if (mAllowCrossDomainRedirect) {
// remember the current, potentially redirected URL if redirects // remember the current, potentially redirected URL if redirects
// were handled by HttpURLConnection // were handled by HttpURLConnection
connectionState.mURL = connectionState.mConnection.getURL(); mURL = mConnection.getURL();
} }
if (response == HttpURLConnection.HTTP_PARTIAL) { if (response == HttpURLConnection.HTTP_PARTIAL) {
@ -292,9 +303,10 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub {
// because what we want is not just the length of the range // because what we want is not just the length of the range
// returned but the size of the full content if available. // returned but the size of the full content if available.
String contentRange = connectionState.mConnection.getHeaderField("Content-Range"); String contentRange =
mConnection.getHeaderField("Content-Range");
connectionState.mTotalSize = -1; mTotalSize = -1;
if (contentRange != null) { if (contentRange != null) {
// format is "bytes xxx-yyy/zzz // format is "bytes xxx-yyy/zzz
// where "zzz" is the total number of bytes of the // where "zzz" is the total number of bytes of the
@ -306,7 +318,7 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub {
contentRange.substring(lastSlashPos + 1); contentRange.substring(lastSlashPos + 1);
try { try {
connectionState.mTotalSize = Long.parseLong(total); mTotalSize = Long.parseLong(total);
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
} }
} }
@ -314,7 +326,7 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub {
} else if (response != HttpURLConnection.HTTP_OK) { } else if (response != HttpURLConnection.HTTP_OK) {
throw new IOException(); throw new IOException();
} else { } else {
connectionState.mTotalSize = connectionState.mConnection.getContentLength(); mTotalSize = mConnection.getContentLength();
} }
if (offset > 0 && response != HttpURLConnection.HTTP_PARTIAL) { if (offset > 0 && response != HttpURLConnection.HTTP_PARTIAL) {
@ -323,14 +335,14 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub {
throw new ProtocolException(); throw new ProtocolException();
} }
connectionState.mInputStream = mInputStream =
new BufferedInputStream(connectionState.mConnection.getInputStream()); new BufferedInputStream(mConnection.getInputStream());
connectionState.mCurrentOffset = offset; mCurrentOffset = offset;
} catch (IOException e) { } catch (IOException e) {
connectionState.mTotalSize = -1; mTotalSize = -1;
teardownConnection(connectionState); teardownConnection();
connectionState.mCurrentOffset = -1; mCurrentOffset = -1;
throw e; throw e;
} }
@ -338,28 +350,22 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub {
@Override @Override
@UnsupportedAppUsage @UnsupportedAppUsage
public int readAt(long offset, int size) { public synchronized int readAt(long offset, int size) {
ConnectionState connectionState = mConnectionStateHolder.get(); return native_readAt(offset, size);
if (connectionState != null) {
return native_readAt(offset, size, connectionState);
}
return -1;
} }
private int readAt(long offset, byte[] data, int size, ConnectionState connectionState) { private synchronized int readAt(long offset, byte[] data, int size) {
StrictMode.ThreadPolicy policy = StrictMode.ThreadPolicy policy =
new StrictMode.ThreadPolicy.Builder().permitAll().build(); new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy); StrictMode.setThreadPolicy(policy);
try { try {
synchronized(this) { if (offset != mCurrentOffset) {
if (offset != connectionState.mCurrentOffset) { seekTo(offset);
seekTo(offset, connectionState);
}
} }
int n = connectionState.mInputStream.read(data, 0, size); int n = mInputStream.read(data, 0, size);
if (n == -1) { if (n == -1) {
// InputStream signals EOS using a -1 result, our semantics // InputStream signals EOS using a -1 result, our semantics
@ -367,7 +373,7 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub {
n = 0; n = 0;
} }
connectionState.mCurrentOffset += n; mCurrentOffset += n;
if (VERBOSE) { if (VERBOSE) {
Log.d(TAG, "readAt " + offset + " / " + size + " => " + n); Log.d(TAG, "readAt " + offset + " / " + size + " => " + n);
@ -399,47 +405,35 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub {
@Override @Override
public synchronized long getSize() { public synchronized long getSize() {
ConnectionState connectionState = mConnectionStateHolder.get(); if (mConnection == null) {
if (connectionState != null) { try {
if (connectionState.mConnection == null) { seekTo(0);
try { } catch (IOException e) {
seekTo(0, connectionState); return -1;
} catch (IOException e) {
return -1;
}
} }
return connectionState.mTotalSize;
} }
return -1; return mTotalSize;
} }
@Override @Override
@UnsupportedAppUsage @UnsupportedAppUsage
public synchronized String getMIMEType() { public synchronized String getMIMEType() {
ConnectionState connectionState = mConnectionStateHolder.get(); if (mConnection == null) {
if (connectionState != null) { try {
if (connectionState.mConnection == null) { seekTo(0);
try { } catch (IOException e) {
seekTo(0, connectionState); return "application/octet-stream";
} catch (IOException e) {
return "application/octet-stream";
}
} }
return connectionState.mConnection.getContentType();
} }
return null; return mConnection.getContentType();
} }
@Override @Override
@UnsupportedAppUsage @UnsupportedAppUsage
public String getUri() { public synchronized String getUri() {
ConnectionState connectionState = mConnectionStateHolder.get(); return mURL.toString();
if (connectionState != null) {
return connectionState.mURL.toString();
}
return null;
} }
@Override @Override
@ -452,7 +446,7 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub {
private native final void native_finalize(); private native final void native_finalize();
private native final IBinder native_getIMemory(); private native final IBinder native_getIMemory();
private native int native_readAt(long offset, int size, ConnectionState connectionState); private native final int native_readAt(long offset, int size);
static { static {
System.loadLibrary("media_jni"); System.loadLibrary("media_jni");

View File

@ -109,8 +109,7 @@ static void android_media_MediaHTTPConnection_native_init(JNIEnv *env) {
gFields.context = env->GetFieldID(clazz.get(), "mNativeContext", "J"); gFields.context = env->GetFieldID(clazz.get(), "mNativeContext", "J");
CHECK(gFields.context != NULL); CHECK(gFields.context != NULL);
gFields.readAtMethodID = env->GetMethodID( gFields.readAtMethodID = env->GetMethodID(clazz.get(), "readAt", "(J[BI)I");
clazz.get(), "readAt", "(J[BILandroid/media/MediaHTTPConnection$ConnectionState;)I");
} }
static void android_media_MediaHTTPConnection_native_setup( static void android_media_MediaHTTPConnection_native_setup(
@ -133,7 +132,7 @@ static jobject android_media_MediaHTTPConnection_native_getIMemory(
} }
static jint android_media_MediaHTTPConnection_native_readAt( static jint android_media_MediaHTTPConnection_native_readAt(
JNIEnv *env, jobject thiz, jlong offset, jint size, jobject connectionState) { JNIEnv *env, jobject thiz, jlong offset, jint size) {
sp<JMediaHTTPConnection> conn = getObject(env, thiz); sp<JMediaHTTPConnection> conn = getObject(env, thiz);
if (size > JMediaHTTPConnection::kBufferSize) { if (size > JMediaHTTPConnection::kBufferSize) {
size = JMediaHTTPConnection::kBufferSize; size = JMediaHTTPConnection::kBufferSize;
@ -142,7 +141,7 @@ static jint android_media_MediaHTTPConnection_native_readAt(
jbyteArray byteArrayObj = conn->getByteArrayObj(); jbyteArray byteArrayObj = conn->getByteArrayObj();
jint n = env->CallIntMethod( jint n = env->CallIntMethod(
thiz, gFields.readAtMethodID, offset, byteArrayObj, size, connectionState); thiz, gFields.readAtMethodID, offset, byteArrayObj, size);
if (n > 0) { if (n > 0) {
env->GetByteArrayRegion( env->GetByteArrayRegion(
@ -159,7 +158,7 @@ static const JNINativeMethod gMethods[] = {
{ "native_getIMemory", "()Landroid/os/IBinder;", { "native_getIMemory", "()Landroid/os/IBinder;",
(void *)android_media_MediaHTTPConnection_native_getIMemory }, (void *)android_media_MediaHTTPConnection_native_getIMemory },
{ "native_readAt", "(JILandroid/media/MediaHTTPConnection$ConnectionState;)I", { "native_readAt", "(JI)I",
(void *)android_media_MediaHTTPConnection_native_readAt }, (void *)android_media_MediaHTTPConnection_native_readAt },
{ "native_init", "()V", { "native_init", "()V",