Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 003ab943 authored by Tobias Thierer's avatar Tobias Thierer Committed by Gerrit Code Review
Browse files

Merge changes I4e32a588,I202c5653

* changes:
  Make MediaHTTPConnection thread safe.
  Revert "MediaHTTPConnection: move connection states into an inner class"
parents cacfab67 8d9fccee
Loading
Loading
Loading
Loading
+119 −125
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import android.os.IBinder;
import android.os.StrictMode;
import android.util.Log;

import com.android.internal.annotations.GuardedBy;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -37,7 +38,6 @@ import java.net.URL;
import java.net.UnknownServiceException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;

/** @hide */
public class MediaHTTPConnection extends IMediaHTTPConnection.Stub {
@@ -47,23 +47,42 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub {
    // connection timeout - 30 sec
    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
    private final static int HTTP_TEMP_REDIRECT = 307;
    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
    public MediaHTTPConnection() {
        CookieHandler cookieHandler = CookieHandler.getDefault();
@@ -76,34 +95,24 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub {

    @Override
    @UnsupportedAppUsage
    public IBinder connect(String uri, String headers) {
    public synchronized IBinder connect(String uri, String headers) {
        if (VERBOSE) {
            Log.d(TAG, "connect: uri=" + uri + ", headers=" + headers);
        }

        ConnectionState connectionState = mConnectionStateHolder.get();
        synchronized (this) {
            if (connectionState == null) {
                connectionState = new ConnectionState();
                mConnectionStateHolder.set(connectionState);
            }
        }

        try {
            disconnect();
            connectionState.mAllowCrossDomainRedirect = true;
            connectionState.mURL = new URL(uri);
            connectionState.mHeaders = convertHeaderStringToMap(headers, connectionState);
            mAllowCrossDomainRedirect = true;
            mURL = new URL(uri);
            mHeaders = convertHeaderStringToMap(headers);
        } catch (MalformedURLException e) {
            return null;
        } finally {
            mConnectionStateHolder.set(connectionState);
        }

        return native_getIMemory();
    }

    private boolean parseBoolean(String val) {
    private static boolean parseBoolean(String val) {
        try {
            return Long.parseLong(val) != 0;
        } catch (NumberFormatException e) {
@@ -113,21 +122,18 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub {
    }

    /* returns true iff header is internal */
    private boolean filterOutInternalHeaders(
            String key, String val, ConnectionState connectionState) {
    private synchronized boolean filterOutInternalHeaders(String key, String val) {
        if ("android-allow-cross-domain-redirect".equalsIgnoreCase(key)) {
            connectionState.mAllowCrossDomainRedirect = parseBoolean(val);
            mAllowCrossDomainRedirect = parseBoolean(val);
            // cross-protocol redirects are also controlled by this flag
            connectionState.mAllowCrossProtocolRedirect =
                    connectionState.mAllowCrossDomainRedirect;
            mAllowCrossProtocolRedirect = mAllowCrossDomainRedirect;
        } else {
            return false;
        }
        return true;
    }

    private Map<String, String> convertHeaderStringToMap(String headers,
            ConnectionState connectionState) {
    private synchronized Map<String, String> convertHeaderStringToMap(String headers) {
        HashMap<String, String> map = new HashMap<String, String>();

        String[] pairs = headers.split("\r\n");
@@ -137,7 +143,7 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub {
                String key = pair.substring(0, colonPos);
                String val = pair.substring(colonPos + 1);

                if (!filterOutInternalHeaders(key, val, connectionState)) {
                if (!filterOutInternalHeaders(key, val)) {
                    map.put(key, val);
                }
            }
@@ -149,28 +155,36 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub {
    @Override
    @UnsupportedAppUsage
    public void disconnect() {
        ConnectionState connectionState = mConnectionStateHolder.getAndSet(null);
        if (connectionState != null) {
            teardownConnection(connectionState);
            connectionState.mHeaders = null;
            connectionState.mURL = null;
        HttpURLConnection connectionToDisconnect = mConnection;
        // Call disconnect() before blocking for the lock in order to ensure that any
        // other thread that is blocked in readAt() will return quickly.
        if (connectionToDisconnect != 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) {
        if (connectionState.mConnection != null) {
            if (connectionState.mInputStream != null) {
    private synchronized void teardownConnection() {
        if (mConnection != null) {
            if (mInputStream != null) {
                try {
                    connectionState.mInputStream.close();
                    mInputStream.close();
                } catch (IOException e) {
                }
                connectionState.mInputStream = null;
                mInputStream = null;
            }

            connectionState.mConnection.disconnect();
            connectionState.mConnection = null;
            mConnection.disconnect();
            mConnection = null;

            connectionState.mCurrentOffset = -1;
            mCurrentOffset = -1;
        }
    }

@@ -197,44 +211,42 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub {
        return false;
    }

    private void seekTo(long offset, ConnectionState connectionState) throws IOException {
        teardownConnection(connectionState);
    private synchronized void seekTo(long offset) throws IOException {
        teardownConnection();

        try {
            int response;
            int redirectCount = 0;

            URL url = connectionState.mURL;
            URL url = mURL;

            // do not use any proxy for localhost (127.0.0.1)
            boolean noProxy = isLocalHost(url);

            while (true) {
                if (noProxy) {
                    connectionState.mConnection =
                            (HttpURLConnection) url.openConnection(Proxy.NO_PROXY);
                    mConnection = (HttpURLConnection)url.openConnection(Proxy.NO_PROXY);
                } 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
                connectionState.mConnection.setInstanceFollowRedirects(
                        connectionState.mAllowCrossDomainRedirect);
                mConnection.setInstanceFollowRedirects(mAllowCrossDomainRedirect);

                if (connectionState.mHeaders != null) {
                    for (Map.Entry<String, String> entry : connectionState.mHeaders.entrySet()) {
                        connectionState.mConnection.setRequestProperty(
                if (mHeaders != null) {
                    for (Map.Entry<String, String> entry : mHeaders.entrySet()) {
                        mConnection.setRequestProperty(
                                entry.getKey(), entry.getValue());
                    }
                }

                if (offset > 0) {
                    connectionState.mConnection.setRequestProperty(
                    mConnection.setRequestProperty(
                            "Range", "bytes=" + offset + "-");
                }

                response = connectionState.mConnection.getResponseCode();
                response = mConnection.getResponseCode();
                if (response != HttpURLConnection.HTTP_MULT_CHOICE &&
                        response != HttpURLConnection.HTTP_MOVED_PERM &&
                        response != HttpURLConnection.HTTP_MOVED_TEMP &&
@@ -248,7 +260,7 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub {
                    throw new NoRouteToHostException("Too many redirects: " + redirectCount);
                }

                String method = connectionState.mConnection.getRequestMethod();
                String method = mConnection.getRequestMethod();
                if (response == HTTP_TEMP_REDIRECT &&
                        !method.equals("GET") && !method.equals("HEAD")) {
                    // "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"
                    throw new NoRouteToHostException("Invalid redirect");
                }
                String location = connectionState.mConnection.getHeaderField("Location");
                String location = mConnection.getHeaderField("Location");
                if (location == null) {
                    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") &&
                        !url.getProtocol().equals("http")) {
                    throw new NoRouteToHostException("Unsupported protocol redirect");
                }
                boolean sameProtocol =
                        connectionState.mURL.getProtocol().equals(url.getProtocol());
                if (!connectionState.mAllowCrossProtocolRedirect && !sameProtocol) {
                boolean sameProtocol = mURL.getProtocol().equals(url.getProtocol());
                if (!mAllowCrossProtocolRedirect && !sameProtocol) {
                    throw new NoRouteToHostException("Cross-protocol redirects are disallowed");
                }
                boolean sameHost = connectionState.mURL.getHost().equals(url.getHost());
                if (!connectionState.mAllowCrossDomainRedirect && !sameHost) {
                boolean sameHost = mURL.getHost().equals(url.getHost());
                if (!mAllowCrossDomainRedirect && !sameHost) {
                    throw new NoRouteToHostException("Cross-domain redirects are disallowed");
                }

                if (response != HTTP_TEMP_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
                // were handled by HttpURLConnection
                connectionState.mURL = connectionState.mConnection.getURL();
                mURL = mConnection.getURL();
            }

            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
                // 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) {
                    // format is "bytes xxx-yyy/zzz
                    // where "zzz" is the total number of bytes of the
@@ -306,7 +318,7 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub {
                            contentRange.substring(lastSlashPos + 1);

                        try {
                            connectionState.mTotalSize = Long.parseLong(total);
                            mTotalSize = Long.parseLong(total);
                        } catch (NumberFormatException e) {
                        }
                    }
@@ -314,7 +326,7 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub {
            } else if (response != HttpURLConnection.HTTP_OK) {
                throw new IOException();
            } else {
                connectionState.mTotalSize = connectionState.mConnection.getContentLength();
                mTotalSize = mConnection.getContentLength();
            }

            if (offset > 0 && response != HttpURLConnection.HTTP_PARTIAL) {
@@ -323,14 +335,14 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub {
                throw new ProtocolException();
            }

            connectionState.mInputStream =
                new BufferedInputStream(connectionState.mConnection.getInputStream());
            mInputStream =
                new BufferedInputStream(mConnection.getInputStream());

            connectionState.mCurrentOffset = offset;
            mCurrentOffset = offset;
        } catch (IOException e) {
            connectionState.mTotalSize = -1;
            teardownConnection(connectionState);
            connectionState.mCurrentOffset = -1;
            mTotalSize = -1;
            teardownConnection();
            mCurrentOffset = -1;

            throw e;
        }
@@ -338,28 +350,22 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub {

    @Override
    @UnsupportedAppUsage
    public int readAt(long offset, int size) {
        ConnectionState connectionState = mConnectionStateHolder.get();
        if (connectionState != null) {
            return native_readAt(offset, size, connectionState);
        }
        return -1;
    public synchronized int readAt(long offset, int size) {
        return native_readAt(offset, size);
    }

    private int readAt(long offset, byte[] data, int size, ConnectionState connectionState) {
    private synchronized int readAt(long offset, byte[] data, int size) {
        StrictMode.ThreadPolicy policy =
            new StrictMode.ThreadPolicy.Builder().permitAll().build();

        StrictMode.setThreadPolicy(policy);

        try {
            synchronized(this) {
                if (offset != connectionState.mCurrentOffset) {
                    seekTo(offset, connectionState);
                }
            if (offset != mCurrentOffset) {
                seekTo(offset);
            }

            int n = connectionState.mInputStream.read(data, 0, size);
            int n = mInputStream.read(data, 0, size);

            if (n == -1) {
                // InputStream signals EOS using a -1 result, our semantics
@@ -367,7 +373,7 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub {
                n = 0;
            }

            connectionState.mCurrentOffset += n;
            mCurrentOffset += n;

            if (VERBOSE) {
                Log.d(TAG, "readAt " + offset + " / " + size + " => " + n);
@@ -399,47 +405,35 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub {

    @Override
    public synchronized long getSize() {
        ConnectionState connectionState = mConnectionStateHolder.get();
        if (connectionState != null) {
            if (connectionState.mConnection == null) {
        if (mConnection == null) {
            try {
                    seekTo(0, connectionState);
                seekTo(0);
            } catch (IOException e) {
                return -1;
            }
        }
            return connectionState.mTotalSize;
        }

        return -1;
        return mTotalSize;
    }

    @Override
    @UnsupportedAppUsage
    public synchronized String getMIMEType() {
        ConnectionState connectionState = mConnectionStateHolder.get();
        if (connectionState != null) {
            if (connectionState.mConnection == null) {
        if (mConnection == null) {
            try {
                    seekTo(0, connectionState);
                seekTo(0);
            } catch (IOException e) {
                return "application/octet-stream";
            }
        }
            return connectionState.mConnection.getContentType();
        }

        return null;
        return mConnection.getContentType();
    }

    @Override
    @UnsupportedAppUsage
    public String getUri() {
        ConnectionState connectionState = mConnectionStateHolder.get();
        if (connectionState != null) {
            return connectionState.mURL.toString();
        }
        return null;
    public synchronized String getUri() {
        return mURL.toString();
    }

    @Override
@@ -452,7 +446,7 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub {
    private native final void native_finalize();

    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 {
        System.loadLibrary("media_jni");
+4 −5
Original line number Diff line number Diff line
@@ -109,8 +109,7 @@ static void android_media_MediaHTTPConnection_native_init(JNIEnv *env) {
    gFields.context = env->GetFieldID(clazz.get(), "mNativeContext", "J");
    CHECK(gFields.context != NULL);

    gFields.readAtMethodID = env->GetMethodID(
            clazz.get(), "readAt", "(J[BILandroid/media/MediaHTTPConnection$ConnectionState;)I");
    gFields.readAtMethodID = env->GetMethodID(clazz.get(), "readAt", "(J[BI)I");
}

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(
        JNIEnv *env, jobject thiz, jlong offset, jint size, jobject connectionState) {
        JNIEnv *env, jobject thiz, jlong offset, jint size) {
    sp<JMediaHTTPConnection> conn = getObject(env, thiz);
    if (size > JMediaHTTPConnection::kBufferSize) {
        size = JMediaHTTPConnection::kBufferSize;
@@ -142,7 +141,7 @@ static jint android_media_MediaHTTPConnection_native_readAt(
    jbyteArray byteArrayObj = conn->getByteArrayObj();

    jint n = env->CallIntMethod(
            thiz, gFields.readAtMethodID, offset, byteArrayObj, size, connectionState);
            thiz, gFields.readAtMethodID, offset, byteArrayObj, size);

    if (n > 0) {
        env->GetByteArrayRegion(
@@ -159,7 +158,7 @@ static const JNINativeMethod gMethods[] = {
    { "native_getIMemory", "()Landroid/os/IBinder;",
      (void *)android_media_MediaHTTPConnection_native_getIMemory },

    { "native_readAt", "(JILandroid/media/MediaHTTPConnection$ConnectionState;)I",
    { "native_readAt", "(JI)I",
      (void *)android_media_MediaHTTPConnection_native_readAt },

    { "native_init", "()V",