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