blob: 5c43a5b5caaabbb59e3c289e78a56df0843e1b2e [file] [log] [blame]
James Donge2693e5d2011-03-05 06:01:44 -08001/*
2 * Copyright (C) 2010 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
Andreas Huber163c4932010-06-10 11:08:43 -070017//#define LOG_NDEBUG 0
18#define LOG_TAG "NuHTTPDataSource"
19#include <utils/Log.h>
20
21#include "include/NuHTTPDataSource.h"
22
Andreas Huber3a53dc52010-06-11 09:57:46 -070023#include <cutils/properties.h>
Andreas Huber2381a8a2010-11-15 08:59:23 -080024#include <media/stagefright/foundation/ALooper.h>
Andreas Huber163c4932010-06-10 11:08:43 -070025#include <media/stagefright/MediaDebug.h>
26#include <media/stagefright/MediaErrors.h>
27
28namespace android {
29
30static bool ParseSingleUnsignedLong(
31 const char *from, unsigned long *x) {
32 char *end;
33 *x = strtoul(from, &end, 10);
34
35 if (end == from || *end != '\0') {
36 return false;
37 }
38
39 return true;
40}
41
42static bool ParseURL(
Andreas Huber118a1502011-02-17 13:35:08 -080043 const char *url, String8 *host, unsigned *port,
44 String8 *path, bool *https) {
Andreas Huber163c4932010-06-10 11:08:43 -070045 host->setTo("");
46 *port = 0;
47 path->setTo("");
48
Andreas Huber118a1502011-02-17 13:35:08 -080049 size_t hostStart;
50 if (!strncasecmp("http://", url, 7)) {
51 hostStart = 7;
52 *https = false;
53 } else if (!strncasecmp("https://", url, 8)) {
54 hostStart = 8;
55 *https = true;
56 } else {
Andreas Huber163c4932010-06-10 11:08:43 -070057 return false;
58 }
59
Andreas Huber118a1502011-02-17 13:35:08 -080060 const char *slashPos = strchr(&url[hostStart], '/');
Andreas Huber163c4932010-06-10 11:08:43 -070061
62 if (slashPos == NULL) {
Andreas Huber118a1502011-02-17 13:35:08 -080063 host->setTo(&url[hostStart]);
Andreas Huber163c4932010-06-10 11:08:43 -070064 path->setTo("/");
65 } else {
Andreas Huber118a1502011-02-17 13:35:08 -080066 host->setTo(&url[hostStart], slashPos - &url[hostStart]);
Andreas Huber163c4932010-06-10 11:08:43 -070067 path->setTo(slashPos);
68 }
69
Mike Lockwood5a23f8c2010-07-15 14:58:25 -040070 const char *colonPos = strchr(host->string(), ':');
Andreas Huber163c4932010-06-10 11:08:43 -070071
72 if (colonPos != NULL) {
73 unsigned long x;
74 if (!ParseSingleUnsignedLong(colonPos + 1, &x) || x >= 65536) {
75 return false;
76 }
77
78 *port = x;
79
80 size_t colonOffset = colonPos - host->string();
81 String8 tmp(host->string(), colonOffset);
82 *host = tmp;
83 } else {
Andreas Huber118a1502011-02-17 13:35:08 -080084 *port = (*https) ? 443 : 80;
Andreas Huber163c4932010-06-10 11:08:43 -070085 }
86
87 return true;
88}
89
Andreas Huber53182c42011-02-24 14:42:48 -080090NuHTTPDataSource::NuHTTPDataSource(uint32_t flags)
91 : mFlags(flags),
92 mState(DISCONNECTED),
Andreas Huber163c4932010-06-10 11:08:43 -070093 mPort(0),
Andreas Huber118a1502011-02-17 13:35:08 -080094 mHTTPS(false),
Andreas Huber163c4932010-06-10 11:08:43 -070095 mOffset(0),
96 mContentLength(0),
Gloria Wangc2c22e72010-11-01 15:53:16 -070097 mContentLengthValid(false),
Andreas Huber9d202762010-11-18 11:03:48 -080098 mHasChunkedTransferEncoding(false),
99 mChunkDataBytesLeft(0),
Andreas Huber2381a8a2010-11-15 08:59:23 -0800100 mNumBandwidthHistoryItems(0),
101 mTotalTransferTimeUs(0),
102 mTotalTransferBytes(0),
Gloria Wangc2c22e72010-11-01 15:53:16 -0700103 mDecryptHandle(NULL),
104 mDrmManagerClient(NULL) {
Andreas Huber163c4932010-06-10 11:08:43 -0700105}
106
107NuHTTPDataSource::~NuHTTPDataSource() {
Gloria Wangc2dc4722011-02-07 11:41:11 -0800108 if (mDecryptHandle != NULL) {
109 // To release mDecryptHandle
Gloria Wangadc4d9c2011-02-08 13:24:08 -0800110 CHECK(mDrmManagerClient);
Gloria Wangc2dc4722011-02-07 11:41:11 -0800111 mDrmManagerClient->closeDecryptSession(mDecryptHandle);
112 mDecryptHandle = NULL;
113 }
114
115 if (mDrmManagerClient != NULL) {
116 delete mDrmManagerClient;
117 mDrmManagerClient = NULL;
118 }
Andreas Huber163c4932010-06-10 11:08:43 -0700119}
120
Andreas Huber3a53dc52010-06-11 09:57:46 -0700121status_t NuHTTPDataSource::connect(
122 const char *uri,
123 const KeyedVector<String8, String8> *overrides,
James Dongb1262a82010-11-16 14:04:54 -0800124 off64_t offset) {
Andreas Huber3a53dc52010-06-11 09:57:46 -0700125 String8 headers;
126 MakeFullHeaders(overrides, &headers);
127
128 return connect(uri, headers, offset);
129}
130
131status_t NuHTTPDataSource::connect(
132 const char *uri,
133 const String8 &headers,
James Dongb1262a82010-11-16 14:04:54 -0800134 off64_t offset) {
Andreas Huber163c4932010-06-10 11:08:43 -0700135 String8 host, path;
136 unsigned port;
Gloria Wangc2c22e72010-11-01 15:53:16 -0700137
138 mUri = uri;
Andreas Hubera2e57ca2011-03-30 11:15:27 -0700139 mContentType = String8("application/octet-stream");
Gloria Wangc2c22e72010-11-01 15:53:16 -0700140
Andreas Huber118a1502011-02-17 13:35:08 -0800141 bool https;
142 if (!ParseURL(uri, &host, &port, &path, &https)) {
Andreas Huber163c4932010-06-10 11:08:43 -0700143 return ERROR_MALFORMED;
144 }
145
Andreas Huber118a1502011-02-17 13:35:08 -0800146 return connect(host, port, path, https, headers, offset);
Andreas Huber163c4932010-06-10 11:08:43 -0700147}
148
Andreas Huberab2116c2010-09-27 08:17:40 -0700149static bool IsRedirectStatusCode(int httpStatus) {
150 return httpStatus == 301 || httpStatus == 302
151 || httpStatus == 303 || httpStatus == 307;
152}
153
Andreas Huber163c4932010-06-10 11:08:43 -0700154status_t NuHTTPDataSource::connect(
Andreas Huber3a53dc52010-06-11 09:57:46 -0700155 const char *host, unsigned port, const char *path,
Andreas Huber118a1502011-02-17 13:35:08 -0800156 bool https,
Andreas Huber3a53dc52010-06-11 09:57:46 -0700157 const String8 &headers,
James Dongb1262a82010-11-16 14:04:54 -0800158 off64_t offset) {
Andreas Huber53182c42011-02-24 14:42:48 -0800159 if (!(mFlags & kFlagIncognito)) {
160 LOGI("connect to %s:%u%s @%lld", host, port, path, offset);
161 } else {
162 LOGI("connect to <URL suppressed> @%lld", offset);
163 }
Andreas Huber163c4932010-06-10 11:08:43 -0700164
165 bool needsToReconnect = true;
166
167 if (mState == CONNECTED && host == mHost && port == mPort
Andreas Huber118a1502011-02-17 13:35:08 -0800168 && https == mHTTPS && offset == mOffset) {
Andreas Huber163c4932010-06-10 11:08:43 -0700169 if (mContentLengthValid && mOffset == mContentLength) {
170 LOGI("Didn't have to reconnect, old one's still good.");
171 needsToReconnect = false;
172 }
173 }
174
175 mHost = host;
176 mPort = port;
177 mPath = path;
Andreas Huber118a1502011-02-17 13:35:08 -0800178 mHTTPS = https;
Andreas Huber3a53dc52010-06-11 09:57:46 -0700179 mHeaders = headers;
Andreas Huber163c4932010-06-10 11:08:43 -0700180
181 status_t err = OK;
182
183 mState = CONNECTING;
184
185 if (needsToReconnect) {
186 mHTTP.disconnect();
Andreas Huber118a1502011-02-17 13:35:08 -0800187 err = mHTTP.connect(host, port, https);
Andreas Huber163c4932010-06-10 11:08:43 -0700188 }
189
190 if (err != OK) {
191 mState = DISCONNECTED;
192 } else if (mState != CONNECTING) {
193 err = UNKNOWN_ERROR;
194 } else {
195 mState = CONNECTED;
196
197 mOffset = offset;
198 mContentLength = 0;
199 mContentLengthValid = false;
200
201 String8 request("GET ");
202 request.append(mPath);
203 request.append(" HTTP/1.1\r\n");
204 request.append("Host: ");
205 request.append(mHost);
Andreas Huber3abb7dd2010-12-16 10:45:24 -0800206 if (mPort != 80) {
207 request.append(StringPrintf(":%u", mPort).c_str());
208 }
Andreas Huber163c4932010-06-10 11:08:43 -0700209 request.append("\r\n");
210
211 if (offset != 0) {
212 char rangeHeader[128];
James Dongb1262a82010-11-16 14:04:54 -0800213 sprintf(rangeHeader, "Range: bytes=%lld-\r\n", offset);
Andreas Huber163c4932010-06-10 11:08:43 -0700214 request.append(rangeHeader);
215 }
216
Andreas Huber3a53dc52010-06-11 09:57:46 -0700217 request.append(mHeaders);
Andreas Huber163c4932010-06-10 11:08:43 -0700218 request.append("\r\n");
219
220 int httpStatus;
221 if ((err = mHTTP.send(request.string(), request.size())) != OK
222 || (err = mHTTP.receive_header(&httpStatus)) != OK) {
223 mHTTP.disconnect();
224 mState = DISCONNECTED;
225 return err;
226 }
227
Andreas Huberab2116c2010-09-27 08:17:40 -0700228 if (IsRedirectStatusCode(httpStatus)) {
Andreas Huber61c79b62010-11-19 09:36:13 -0800229 AString value;
Andreas Huber163c4932010-06-10 11:08:43 -0700230 CHECK(mHTTP.find_header_value("Location", &value));
231
232 mState = DISCONNECTED;
233
234 mHTTP.disconnect();
235
Andreas Huber3a53dc52010-06-11 09:57:46 -0700236 return connect(value.c_str(), headers, offset);
Andreas Huber163c4932010-06-10 11:08:43 -0700237 }
238
Andreas Huber3a53dc52010-06-11 09:57:46 -0700239 if (httpStatus < 200 || httpStatus >= 300) {
240 mState = DISCONNECTED;
241 mHTTP.disconnect();
242
243 return ERROR_IO;
244 }
245
Andreas Huber9d202762010-11-18 11:03:48 -0800246 mHasChunkedTransferEncoding = false;
247
Andreas Huber2381a8a2010-11-15 08:59:23 -0800248 {
Andreas Huber61c79b62010-11-19 09:36:13 -0800249 AString value;
250 if (mHTTP.find_header_value("Transfer-Encoding", &value)) {
Andreas Huber9d202762010-11-18 11:03:48 -0800251 // We don't currently support any transfer encodings but
252 // chunked.
Andreas Huber2381a8a2010-11-15 08:59:23 -0800253
Andreas Huber9d202762010-11-18 11:03:48 -0800254 if (!strcasecmp(value.c_str(), "chunked")) {
255 LOGI("Chunked transfer encoding applied.");
256 mHasChunkedTransferEncoding = true;
257 mChunkDataBytesLeft = 0;
258 } else {
259 mState = DISCONNECTED;
260 mHTTP.disconnect();
Andreas Huber2381a8a2010-11-15 08:59:23 -0800261
Andreas Huber9d202762010-11-18 11:03:48 -0800262 LOGE("We don't support '%s' transfer encoding.", value.c_str());
Andreas Huber2381a8a2010-11-15 08:59:23 -0800263
Andreas Huber9d202762010-11-18 11:03:48 -0800264 return ERROR_UNSUPPORTED;
265 }
Andreas Huber2381a8a2010-11-15 08:59:23 -0800266 }
267 }
268
Andreas Hubera2e57ca2011-03-30 11:15:27 -0700269 {
270 AString value;
271 if (mHTTP.find_header_value("Content-Type", &value)) {
272 mContentType = String8(value.c_str());
273 } else {
274 mContentType = String8("application/octet-stream");
275 }
276 }
277
Andreas Huber3a53dc52010-06-11 09:57:46 -0700278 applyTimeoutResponse();
Andreas Huber163c4932010-06-10 11:08:43 -0700279
280 if (offset == 0) {
Andreas Huber61c79b62010-11-19 09:36:13 -0800281 AString value;
Andreas Huber163c4932010-06-10 11:08:43 -0700282 unsigned long x;
Andreas Huber61c79b62010-11-19 09:36:13 -0800283 if (mHTTP.find_header_value(AString("Content-Length"), &value)
Andreas Huber163c4932010-06-10 11:08:43 -0700284 && ParseSingleUnsignedLong(value.c_str(), &x)) {
James Dongb1262a82010-11-16 14:04:54 -0800285 mContentLength = (off64_t)x;
Andreas Huber163c4932010-06-10 11:08:43 -0700286 mContentLengthValid = true;
Andreas Huber9d202762010-11-18 11:03:48 -0800287 } else {
288 LOGW("Server did not give us the content length!");
Andreas Huber163c4932010-06-10 11:08:43 -0700289 }
290 } else {
Andreas Huber9d202762010-11-18 11:03:48 -0800291 if (httpStatus != 206 /* Partial Content */) {
292 // We requested a range but the server didn't support that.
293 LOGE("We requested a range but the server didn't "
294 "support that.");
295 return ERROR_UNSUPPORTED;
296 }
297
Andreas Huber61c79b62010-11-19 09:36:13 -0800298 AString value;
Andreas Huber163c4932010-06-10 11:08:43 -0700299 unsigned long x;
Andreas Huber61c79b62010-11-19 09:36:13 -0800300 if (mHTTP.find_header_value(AString("Content-Range"), &value)) {
Andreas Huber163c4932010-06-10 11:08:43 -0700301 const char *slashPos = strchr(value.c_str(), '/');
302 if (slashPos != NULL
303 && ParseSingleUnsignedLong(slashPos + 1, &x)) {
304 mContentLength = x;
305 mContentLengthValid = true;
306 }
307 }
308 }
309 }
310
311 return err;
312}
313
314void NuHTTPDataSource::disconnect() {
315 if (mState == CONNECTING || mState == CONNECTED) {
316 mHTTP.disconnect();
317 }
318 mState = DISCONNECTED;
319}
320
321status_t NuHTTPDataSource::initCheck() const {
322 return mState == CONNECTED ? OK : NO_INIT;
323}
324
Andreas Huber9d202762010-11-18 11:03:48 -0800325ssize_t NuHTTPDataSource::internalRead(void *data, size_t size) {
326 if (!mHasChunkedTransferEncoding) {
327 return mHTTP.receive(data, size);
328 }
329
330 if (mChunkDataBytesLeft < 0) {
331 return 0;
332 } else if (mChunkDataBytesLeft == 0) {
333 char line[1024];
334 status_t err = mHTTP.receive_line(line, sizeof(line));
335
336 if (err != OK) {
337 return err;
338 }
339
340 LOGV("line = '%s'", line);
341
342 char *end;
343 unsigned long n = strtoul(line, &end, 16);
344
345 if (end == line || (*end != ';' && *end != '\0')) {
346 LOGE("malformed HTTP chunk '%s'", line);
347 return ERROR_MALFORMED;
348 }
349
350 mChunkDataBytesLeft = n;
351 LOGV("chunk data size = %lu", n);
352
353 if (mChunkDataBytesLeft == 0) {
354 mChunkDataBytesLeft = -1;
355 return 0;
356 }
357
358 // fall through
359 }
360
361 if (size > (size_t)mChunkDataBytesLeft) {
362 size = mChunkDataBytesLeft;
363 }
364
365 ssize_t n = mHTTP.receive(data, size);
366
367 if (n < 0) {
368 return n;
369 }
370
371 mChunkDataBytesLeft -= (size_t)n;
372
373 if (mChunkDataBytesLeft == 0) {
374 char line[1024];
375 status_t err = mHTTP.receive_line(line, sizeof(line));
376
377 if (err != OK) {
378 return err;
379 }
380
381 if (line[0] != '\0') {
382 LOGE("missing HTTP chunk terminator.");
383 return ERROR_MALFORMED;
384 }
385 }
386
387 return n;
388}
389
James Dongb1262a82010-11-16 14:04:54 -0800390ssize_t NuHTTPDataSource::readAt(off64_t offset, void *data, size_t size) {
Andreas Huber163c4932010-06-10 11:08:43 -0700391 LOGV("readAt offset %ld, size %d", offset, size);
392
393 Mutex::Autolock autoLock(mLock);
394
395 if (offset != mOffset) {
396 String8 host = mHost;
397 String8 path = mPath;
Andreas Huber3a53dc52010-06-11 09:57:46 -0700398 String8 headers = mHeaders;
Andreas Huber118a1502011-02-17 13:35:08 -0800399 status_t err = connect(host, mPort, path, mHTTPS, headers, offset);
Andreas Huber163c4932010-06-10 11:08:43 -0700400
401 if (err != OK) {
402 return err;
403 }
404 }
405
406 if (mContentLengthValid) {
407 size_t avail =
408 (offset >= mContentLength) ? 0 : mContentLength - offset;
409
410 if (size > avail) {
411 size = avail;
412 }
413 }
414
415 size_t numBytesRead = 0;
416 while (numBytesRead < size) {
Andreas Huber2381a8a2010-11-15 08:59:23 -0800417 int64_t startTimeUs = ALooper::GetNowUs();
418
Andreas Huber163c4932010-06-10 11:08:43 -0700419 ssize_t n =
Andreas Huber9d202762010-11-18 11:03:48 -0800420 internalRead((uint8_t *)data + numBytesRead, size - numBytesRead);
Andreas Huber163c4932010-06-10 11:08:43 -0700421
422 if (n < 0) {
423 return n;
424 }
425
Andreas Huber2381a8a2010-11-15 08:59:23 -0800426 int64_t delayUs = ALooper::GetNowUs() - startTimeUs;
427 addBandwidthMeasurement_l(n, delayUs);
428
Andreas Huber163c4932010-06-10 11:08:43 -0700429 numBytesRead += (size_t)n;
430
431 if (n == 0) {
432 if (mContentLengthValid) {
433 // We know the content length and made sure not to read beyond
434 // it and yet the server closed the connection on us.
435 return ERROR_IO;
436 }
437
438 break;
439 }
440 }
441
442 mOffset += numBytesRead;
443
444 return numBytesRead;
445}
446
James Dongb1262a82010-11-16 14:04:54 -0800447status_t NuHTTPDataSource::getSize(off64_t *size) {
Andreas Huber163c4932010-06-10 11:08:43 -0700448 *size = 0;
449
450 if (mState != CONNECTED) {
451 return ERROR_IO;
452 }
453
454 if (mContentLengthValid) {
455 *size = mContentLength;
456 return OK;
457 }
458
459 return ERROR_UNSUPPORTED;
460}
461
462uint32_t NuHTTPDataSource::flags() {
463 return kWantsPrefetching;
464}
465
Andreas Huber3a53dc52010-06-11 09:57:46 -0700466// static
467void NuHTTPDataSource::MakeFullHeaders(
468 const KeyedVector<String8, String8> *overrides, String8 *headers) {
469 headers->setTo("");
470
471 headers->append("User-Agent: stagefright/1.1 (Linux;Android ");
472
473#if (PROPERTY_VALUE_MAX < 8)
474#error "PROPERTY_VALUE_MAX must be at least 8"
475#endif
476
477 char value[PROPERTY_VALUE_MAX];
478 property_get("ro.build.version.release", value, "Unknown");
479 headers->append(value);
480 headers->append(")\r\n");
481
482 if (overrides == NULL) {
483 return;
484 }
485
486 for (size_t i = 0; i < overrides->size(); ++i) {
487 String8 line;
488 line.append(overrides->keyAt(i));
489 line.append(": ");
490 line.append(overrides->valueAt(i));
491 line.append("\r\n");
492
493 headers->append(line);
494 }
495}
496
497void NuHTTPDataSource::applyTimeoutResponse() {
Andreas Huber61c79b62010-11-19 09:36:13 -0800498 AString timeout;
Andreas Huber3a53dc52010-06-11 09:57:46 -0700499 if (mHTTP.find_header_value("X-SocketTimeout", &timeout)) {
500 const char *s = timeout.c_str();
501 char *end;
502 long tmp = strtol(s, &end, 10);
503 if (end == s || *end != '\0') {
504 LOGW("Illegal X-SocketTimeout value given.");
505 return;
506 }
507
508 LOGI("overriding default timeout, new timeout is %ld seconds", tmp);
509 mHTTP.setReceiveTimeout(tmp);
510 }
511}
512
Andreas Huber2381a8a2010-11-15 08:59:23 -0800513bool NuHTTPDataSource::estimateBandwidth(int32_t *bandwidth_bps) {
514 Mutex::Autolock autoLock(mLock);
515
Andreas Huberb5590842010-12-03 16:12:25 -0800516 if (mNumBandwidthHistoryItems < 2) {
Andreas Huber2381a8a2010-11-15 08:59:23 -0800517 return false;
518 }
519
520 *bandwidth_bps = ((double)mTotalTransferBytes * 8E6 / mTotalTransferTimeUs);
521
522 return true;
523}
524
525void NuHTTPDataSource::addBandwidthMeasurement_l(
526 size_t numBytes, int64_t delayUs) {
527 BandwidthEntry entry;
528 entry.mDelayUs = delayUs;
529 entry.mNumBytes = numBytes;
530 mTotalTransferTimeUs += delayUs;
531 mTotalTransferBytes += numBytes;
532
533 mBandwidthHistory.push_back(entry);
534 if (++mNumBandwidthHistoryItems > 100) {
535 BandwidthEntry *entry = &*mBandwidthHistory.begin();
536 mTotalTransferTimeUs -= entry->mDelayUs;
537 mTotalTransferBytes -= entry->mNumBytes;
538 mBandwidthHistory.erase(mBandwidthHistory.begin());
539 --mNumBandwidthHistoryItems;
540 }
541}
542
Gloria Wangae775272011-02-24 16:40:57 -0800543sp<DecryptHandle> NuHTTPDataSource::DrmInitialization() {
Gloria Wangc2dc4722011-02-07 11:41:11 -0800544 if (mDrmManagerClient == NULL) {
545 mDrmManagerClient = new DrmManagerClient();
546 }
547
548 if (mDrmManagerClient == NULL) {
Gloria Wangc2c22e72010-11-01 15:53:16 -0700549 return NULL;
550 }
Gloria Wangc2c22e72010-11-01 15:53:16 -0700551
552 if (mDecryptHandle == NULL) {
553 /* Note if redirect occurs, mUri is the redirect uri instead of the
554 * original one
555 */
556 mDecryptHandle = mDrmManagerClient->openDecryptSession(mUri);
557 }
558
559 if (mDecryptHandle == NULL) {
Gloria Wangc2dc4722011-02-07 11:41:11 -0800560 delete mDrmManagerClient;
Gloria Wangc2c22e72010-11-01 15:53:16 -0700561 mDrmManagerClient = NULL;
562 }
563
564 return mDecryptHandle;
565}
566
Gloria Wangae775272011-02-24 16:40:57 -0800567void NuHTTPDataSource::getDrmInfo(sp<DecryptHandle> &handle, DrmManagerClient **client) {
568 handle = mDecryptHandle;
Gloria Wangc2c22e72010-11-01 15:53:16 -0700569
570 *client = mDrmManagerClient;
571}
572
Gloria Wang43cd12d2010-11-09 15:06:51 -0800573String8 NuHTTPDataSource::getUri() {
574 return mUri;
575}
576
Andreas Hubera2e57ca2011-03-30 11:15:27 -0700577String8 NuHTTPDataSource::getMIMEType() const {
578 return mContentType;
579}
580
Andreas Huber163c4932010-06-10 11:08:43 -0700581} // namespace android