MediaHTTPConnection.java revision 6e89ddc0468495aa15c8408980bb7a86bf2ad604
1/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.media;
18
19import android.os.IBinder;
20import android.os.StrictMode;
21import android.util.Log;
22
23import java.io.BufferedInputStream;
24import java.io.InputStream;
25import java.io.IOException;
26import java.net.CookieHandler;
27import java.net.CookieManager;
28import java.net.URL;
29import java.net.HttpURLConnection;
30import java.net.MalformedURLException;
31import java.util.HashMap;
32import java.util.Map;
33
34/** @hide */
35public class MediaHTTPConnection extends IMediaHTTPConnection.Stub {
36    private static final String TAG = "MediaHTTPConnection";
37    private static final boolean VERBOSE = false;
38
39    private long mCurrentOffset = -1;
40    private URL mURL = null;
41    private Map<String, String> mHeaders = null;
42    private HttpURLConnection mConnection = null;
43    private long mTotalSize = -1;
44    private InputStream mInputStream = null;
45
46    public MediaHTTPConnection() {
47        if (CookieHandler.getDefault() == null) {
48            CookieHandler.setDefault(new CookieManager());
49        }
50
51        native_setup();
52    }
53
54    @Override
55    public IBinder connect(String uri, String headers) {
56        if (VERBOSE) {
57            Log.d(TAG, "connect: uri=" + uri + ", headers=" + headers);
58        }
59
60        try {
61            disconnect();
62            mURL = new URL(uri);
63            mHeaders = convertHeaderStringToMap(headers);
64        } catch (MalformedURLException e) {
65            return null;
66        }
67
68        return native_getIMemory();
69    }
70
71    private Map<String, String> convertHeaderStringToMap(String headers) {
72        HashMap<String, String> map = new HashMap<String, String>();
73
74        String[] pairs = headers.split("\r\n");
75        for (String pair : pairs) {
76            int colonPos = pair.indexOf(":");
77            if (colonPos >= 0) {
78                String key = pair.substring(0, colonPos);
79                String val = pair.substring(colonPos + 1);
80
81                map.put(key, val);
82            }
83        }
84
85        return map;
86    }
87
88    @Override
89    public void disconnect() {
90        teardownConnection();
91        mHeaders = null;
92        mURL = null;
93    }
94
95    private void teardownConnection() {
96        if (mConnection != null) {
97            mInputStream = null;
98
99            mConnection.disconnect();
100            mConnection = null;
101
102            mCurrentOffset = -1;
103        }
104    }
105
106    private void seekTo(long offset) throws IOException {
107        teardownConnection();
108
109        try {
110            mConnection = (HttpURLConnection)mURL.openConnection();
111
112            if (mHeaders != null) {
113                for (Map.Entry<String, String> entry : mHeaders.entrySet()) {
114                    mConnection.setRequestProperty(
115                            entry.getKey(), entry.getValue());
116                }
117            }
118
119            if (offset > 0) {
120                mConnection.setRequestProperty(
121                        "Range", "bytes=" + offset + "-");
122            }
123
124            int response = mConnection.getResponseCode();
125            // remember the current, possibly redirected URL
126            mURL = mConnection.getURL();
127
128            if (response == HttpURLConnection.HTTP_PARTIAL) {
129                // Partial content, we cannot just use getContentLength
130                // because what we want is not just the length of the range
131                // returned but the size of the full content if available.
132
133                String contentRange =
134                    mConnection.getHeaderField("Content-Range");
135
136                mTotalSize = -1;
137                if (contentRange != null) {
138                    // format is "bytes xxx-yyy/zzz
139                    // where "zzz" is the total number of bytes of the
140                    // content or '*' if unknown.
141
142                    int lastSlashPos = contentRange.lastIndexOf('/');
143                    if (lastSlashPos >= 0) {
144                        String total =
145                            contentRange.substring(lastSlashPos + 1);
146
147                        try {
148                            mTotalSize = Long.parseLong(total);
149                        } catch (NumberFormatException e) {
150                        }
151                    }
152                }
153            } else if (response != HttpURLConnection.HTTP_OK) {
154                throw new IOException();
155            } else {
156                mTotalSize = mConnection.getContentLength();
157            }
158
159            if (offset > 0 && response != HttpURLConnection.HTTP_PARTIAL) {
160                // Some servers simply ignore "Range" requests and serve
161                // data from the start of the content.
162                throw new IOException();
163            }
164
165            mInputStream =
166                new BufferedInputStream(mConnection.getInputStream());
167
168            mCurrentOffset = offset;
169        } catch (IOException e) {
170            mTotalSize = -1;
171            mInputStream = null;
172            mConnection = null;
173            mCurrentOffset = -1;
174
175            throw e;
176        }
177    }
178
179    @Override
180    public int readAt(long offset, int size) {
181        return native_readAt(offset, size);
182    }
183
184    private int readAt(long offset, byte[] data, int size) {
185        StrictMode.ThreadPolicy policy =
186            new StrictMode.ThreadPolicy.Builder().permitAll().build();
187
188        StrictMode.setThreadPolicy(policy);
189
190        try {
191            if (offset != mCurrentOffset) {
192                seekTo(offset);
193            }
194
195            int n = mInputStream.read(data, 0, size);
196
197            if (n == -1) {
198                // InputStream signals EOS using a -1 result, our semantics
199                // are to return a 0-length read.
200                n = 0;
201            }
202
203            mCurrentOffset += n;
204
205            if (VERBOSE) {
206                Log.d(TAG, "readAt " + offset + " / " + size + " => " + n);
207            }
208
209            return n;
210        } catch (IOException e) {
211            if (VERBOSE) {
212                Log.d(TAG, "readAt " + offset + " / " + size + " => -1");
213            }
214            return -1;
215        } catch (Exception e) {
216            if (VERBOSE) {
217                Log.d(TAG, "unknown exception " + e);
218                Log.d(TAG, "readAt " + offset + " / " + size + " => -1");
219            }
220            return -1;
221        }
222    }
223
224    @Override
225    public long getSize() {
226        if (mConnection == null) {
227            try {
228                seekTo(0);
229            } catch (IOException e) {
230                return -1;
231            }
232        }
233
234        return mTotalSize;
235    }
236
237    @Override
238    public String getMIMEType() {
239        if (mConnection == null) {
240            try {
241                seekTo(0);
242            } catch (IOException e) {
243                return "application/octet-stream";
244            }
245        }
246
247        return mConnection.getContentType();
248    }
249
250    @Override
251    public String getUri() {
252        return mURL.toString();
253    }
254
255    @Override
256    protected void finalize() {
257        native_finalize();
258    }
259
260    private static native final void native_init();
261    private native final void native_setup();
262    private native final void native_finalize();
263
264    private native final IBinder native_getIMemory();
265    private native final int native_readAt(long offset, int size);
266
267    static {
268        System.loadLibrary("media_jni");
269        native_init();
270    }
271
272    private int mNativeContext;
273
274}
275