1 /*
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
17 #include <inttypes.h>
18
19 //#define LOG_NDEBUG 0
20 #define LOG_TAG "NuCachedSource2"
21 #include <utils/Log.h>
22
23 #include "include/NuCachedSource2.h"
24 #include "include/HTTPBase.h"
25
26 #include <cutils/properties.h>
27 #include <media/stagefright/foundation/ADebug.h>
28 #include <media/stagefright/foundation/AMessage.h>
29 #include <media/stagefright/MediaErrors.h>
30
31 namespace android {
32
33 struct PageCache {
34 PageCache(size_t pageSize);
35 ~PageCache();
36
37 struct Page {
38 void *mData;
39 size_t mSize;
40 };
41
42 Page *acquirePage();
43 void releasePage(Page *page);
44
45 void appendPage(Page *page);
46 size_t releaseFromStart(size_t maxBytes);
47
totalSizeandroid::PageCache48 size_t totalSize() const {
49 return mTotalSize;
50 }
51
52 void copy(size_t from, void *data, size_t size);
53
54 private:
55 size_t mPageSize;
56 size_t mTotalSize;
57
58 List<Page *> mActivePages;
59 List<Page *> mFreePages;
60
61 void freePages(List<Page *> *list);
62
63 DISALLOW_EVIL_CONSTRUCTORS(PageCache);
64 };
65
PageCache(size_t pageSize)66 PageCache::PageCache(size_t pageSize)
67 : mPageSize(pageSize),
68 mTotalSize(0) {
69 }
70
~PageCache()71 PageCache::~PageCache() {
72 freePages(&mActivePages);
73 freePages(&mFreePages);
74 }
75
freePages(List<Page * > * list)76 void PageCache::freePages(List<Page *> *list) {
77 List<Page *>::iterator it = list->begin();
78 while (it != list->end()) {
79 Page *page = *it;
80
81 free(page->mData);
82 delete page;
83 page = NULL;
84
85 ++it;
86 }
87 }
88
acquirePage()89 PageCache::Page *PageCache::acquirePage() {
90 if (!mFreePages.empty()) {
91 List<Page *>::iterator it = mFreePages.begin();
92 Page *page = *it;
93 mFreePages.erase(it);
94
95 return page;
96 }
97
98 Page *page = new Page;
99 page->mData = malloc(mPageSize);
100 page->mSize = 0;
101
102 return page;
103 }
104
releasePage(Page * page)105 void PageCache::releasePage(Page *page) {
106 page->mSize = 0;
107 mFreePages.push_back(page);
108 }
109
appendPage(Page * page)110 void PageCache::appendPage(Page *page) {
111 mTotalSize += page->mSize;
112 mActivePages.push_back(page);
113 }
114
releaseFromStart(size_t maxBytes)115 size_t PageCache::releaseFromStart(size_t maxBytes) {
116 size_t bytesReleased = 0;
117
118 while (maxBytes > 0 && !mActivePages.empty()) {
119 List<Page *>::iterator it = mActivePages.begin();
120
121 Page *page = *it;
122
123 if (maxBytes < page->mSize) {
124 break;
125 }
126
127 mActivePages.erase(it);
128
129 maxBytes -= page->mSize;
130 bytesReleased += page->mSize;
131
132 releasePage(page);
133 }
134
135 mTotalSize -= bytesReleased;
136 return bytesReleased;
137 }
138
copy(size_t from,void * data,size_t size)139 void PageCache::copy(size_t from, void *data, size_t size) {
140 ALOGV("copy from %zu size %zu", from, size);
141
142 if (size == 0) {
143 return;
144 }
145
146 CHECK_LE(from + size, mTotalSize);
147
148 size_t offset = 0;
149 List<Page *>::iterator it = mActivePages.begin();
150 while (from >= offset + (*it)->mSize) {
151 offset += (*it)->mSize;
152 ++it;
153 }
154
155 size_t delta = from - offset;
156 size_t avail = (*it)->mSize - delta;
157
158 if (avail >= size) {
159 memcpy(data, (const uint8_t *)(*it)->mData + delta, size);
160 return;
161 }
162
163 memcpy(data, (const uint8_t *)(*it)->mData + delta, avail);
164 ++it;
165 data = (uint8_t *)data + avail;
166 size -= avail;
167
168 while (size > 0) {
169 size_t copy = (*it)->mSize;
170 if (copy > size) {
171 copy = size;
172 }
173 memcpy(data, (*it)->mData, copy);
174 data = (uint8_t *)data + copy;
175 size -= copy;
176 ++it;
177 }
178 }
179
180 ////////////////////////////////////////////////////////////////////////////////
181
NuCachedSource2(const sp<DataSource> & source,const char * cacheConfig,bool disconnectAtHighwatermark)182 NuCachedSource2::NuCachedSource2(
183 const sp<DataSource> &source,
184 const char *cacheConfig,
185 bool disconnectAtHighwatermark)
186 : mSource(source),
187 mReflector(new AHandlerReflector<NuCachedSource2>(this)),
188 mLooper(new ALooper),
189 mCache(new PageCache(kPageSize)),
190 mCacheOffset(0),
191 mFinalStatus(OK),
192 mLastAccessPos(0),
193 mFetching(true),
194 mDisconnecting(false),
195 mLastFetchTimeUs(-1),
196 mNumRetriesLeft(kMaxNumRetries),
197 mHighwaterThresholdBytes(kDefaultHighWaterThreshold),
198 mLowwaterThresholdBytes(kDefaultLowWaterThreshold),
199 mKeepAliveIntervalUs(kDefaultKeepAliveIntervalUs),
200 mDisconnectAtHighwatermark(disconnectAtHighwatermark) {
201 // We are NOT going to support disconnect-at-highwatermark indefinitely
202 // and we are not guaranteeing support for client-specified cache
203 // parameters. Both of these are temporary measures to solve a specific
204 // problem that will be solved in a better way going forward.
205
206 updateCacheParamsFromSystemProperty();
207
208 if (cacheConfig != NULL) {
209 updateCacheParamsFromString(cacheConfig);
210 }
211
212 if (mDisconnectAtHighwatermark) {
213 // Makes no sense to disconnect and do keep-alives...
214 mKeepAliveIntervalUs = 0;
215 }
216
217 mLooper->setName("NuCachedSource2");
218 mLooper->registerHandler(mReflector);
219
220 // Since it may not be obvious why our looper thread needs to be
221 // able to call into java since it doesn't appear to do so at all...
222 // IMediaHTTPConnection may be (and most likely is) implemented in JAVA
223 // and a local JAVA IBinder will call directly into JNI methods.
224 // So whenever we call DataSource::readAt it may end up in a call to
225 // IMediaHTTPConnection::readAt and therefore call back into JAVA.
226 mLooper->start(false /* runOnCallingThread */, true /* canCallJava */);
227
228 Mutex::Autolock autoLock(mLock);
229 (new AMessage(kWhatFetchMore, mReflector))->post();
230 }
231
~NuCachedSource2()232 NuCachedSource2::~NuCachedSource2() {
233 mLooper->stop();
234 mLooper->unregisterHandler(mReflector->id());
235
236 delete mCache;
237 mCache = NULL;
238 }
239
getEstimatedBandwidthKbps(int32_t * kbps)240 status_t NuCachedSource2::getEstimatedBandwidthKbps(int32_t *kbps) {
241 if (mSource->flags() & kIsHTTPBasedSource) {
242 HTTPBase* source = static_cast<HTTPBase *>(mSource.get());
243 return source->getEstimatedBandwidthKbps(kbps);
244 }
245 return ERROR_UNSUPPORTED;
246 }
247
disconnect()248 void NuCachedSource2::disconnect() {
249 if (mSource->flags() & kIsHTTPBasedSource) {
250 ALOGV("disconnecting HTTPBasedSource");
251
252 {
253 Mutex::Autolock autoLock(mLock);
254 // set mDisconnecting to true, if a fetch returns after
255 // this, the source will be marked as EOS.
256 mDisconnecting = true;
257
258 // explicitly signal mCondition so that the pending readAt()
259 // will immediately return
260 mCondition.signal();
261 }
262
263 // explicitly disconnect from the source, to allow any
264 // pending reads to return more promptly
265 static_cast<HTTPBase *>(mSource.get())->disconnect();
266 }
267 }
268
setCacheStatCollectFreq(int32_t freqMs)269 status_t NuCachedSource2::setCacheStatCollectFreq(int32_t freqMs) {
270 if (mSource->flags() & kIsHTTPBasedSource) {
271 HTTPBase *source = static_cast<HTTPBase *>(mSource.get());
272 return source->setBandwidthStatCollectFreq(freqMs);
273 }
274 return ERROR_UNSUPPORTED;
275 }
276
initCheck() const277 status_t NuCachedSource2::initCheck() const {
278 return mSource->initCheck();
279 }
280
getSize(off64_t * size)281 status_t NuCachedSource2::getSize(off64_t *size) {
282 return mSource->getSize(size);
283 }
284
flags()285 uint32_t NuCachedSource2::flags() {
286 // Remove HTTP related flags since NuCachedSource2 is not HTTP-based.
287 uint32_t flags = mSource->flags() & ~(kWantsPrefetching | kIsHTTPBasedSource);
288 return (flags | kIsCachingDataSource);
289 }
290
onMessageReceived(const sp<AMessage> & msg)291 void NuCachedSource2::onMessageReceived(const sp<AMessage> &msg) {
292 switch (msg->what()) {
293 case kWhatFetchMore:
294 {
295 onFetch();
296 break;
297 }
298
299 case kWhatRead:
300 {
301 onRead(msg);
302 break;
303 }
304
305 default:
306 TRESPASS();
307 }
308 }
309
fetchInternal()310 void NuCachedSource2::fetchInternal() {
311 ALOGV("fetchInternal");
312
313 bool reconnect = false;
314
315 {
316 Mutex::Autolock autoLock(mLock);
317 CHECK(mFinalStatus == OK || mNumRetriesLeft > 0);
318
319 if (mFinalStatus != OK) {
320 --mNumRetriesLeft;
321
322 reconnect = true;
323 }
324 }
325
326 if (reconnect) {
327 status_t err =
328 mSource->reconnectAtOffset(mCacheOffset + mCache->totalSize());
329
330 Mutex::Autolock autoLock(mLock);
331
332 if (mDisconnecting) {
333 mNumRetriesLeft = 0;
334 mFinalStatus = ERROR_END_OF_STREAM;
335 return;
336 } else if (err == ERROR_UNSUPPORTED || err == -EPIPE) {
337 // These are errors that are not likely to go away even if we
338 // retry, i.e. the server doesn't support range requests or similar.
339 mNumRetriesLeft = 0;
340 return;
341 } else if (err != OK) {
342 ALOGI("The attempt to reconnect failed, %d retries remaining",
343 mNumRetriesLeft);
344
345 return;
346 }
347 }
348
349 PageCache::Page *page = mCache->acquirePage();
350
351 ssize_t n = mSource->readAt(
352 mCacheOffset + mCache->totalSize(), page->mData, kPageSize);
353
354 Mutex::Autolock autoLock(mLock);
355
356 if (n == 0 || mDisconnecting) {
357 ALOGI("caching reached eos.");
358
359 mNumRetriesLeft = 0;
360 mFinalStatus = ERROR_END_OF_STREAM;
361
362 mCache->releasePage(page);
363 } else if (n < 0) {
364 mFinalStatus = n;
365 if (n == ERROR_UNSUPPORTED || n == -EPIPE) {
366 // These are errors that are not likely to go away even if we
367 // retry, i.e. the server doesn't support range requests or similar.
368 mNumRetriesLeft = 0;
369 }
370
371 ALOGE("source returned error %zd, %d retries left", n, mNumRetriesLeft);
372 mCache->releasePage(page);
373 } else {
374 if (mFinalStatus != OK) {
375 ALOGI("retrying a previously failed read succeeded.");
376 }
377 mNumRetriesLeft = kMaxNumRetries;
378 mFinalStatus = OK;
379
380 page->mSize = n;
381 mCache->appendPage(page);
382 }
383 }
384
onFetch()385 void NuCachedSource2::onFetch() {
386 ALOGV("onFetch");
387
388 if (mFinalStatus != OK && mNumRetriesLeft == 0) {
389 ALOGV("EOS reached, done prefetching for now");
390 mFetching = false;
391 }
392
393 bool keepAlive =
394 !mFetching
395 && mFinalStatus == OK
396 && mKeepAliveIntervalUs > 0
397 && ALooper::GetNowUs() >= mLastFetchTimeUs + mKeepAliveIntervalUs;
398
399 if (mFetching || keepAlive) {
400 if (keepAlive) {
401 ALOGI("Keep alive");
402 }
403
404 fetchInternal();
405
406 mLastFetchTimeUs = ALooper::GetNowUs();
407
408 if (mFetching && mCache->totalSize() >= mHighwaterThresholdBytes) {
409 ALOGI("Cache full, done prefetching for now");
410 mFetching = false;
411
412 if (mDisconnectAtHighwatermark
413 && (mSource->flags() & DataSource::kIsHTTPBasedSource)) {
414 ALOGV("Disconnecting at high watermark");
415 static_cast<HTTPBase *>(mSource.get())->disconnect();
416 mFinalStatus = -EAGAIN;
417 }
418 }
419 } else {
420 Mutex::Autolock autoLock(mLock);
421 restartPrefetcherIfNecessary_l();
422 }
423
424 int64_t delayUs;
425 if (mFetching) {
426 if (mFinalStatus != OK && mNumRetriesLeft > 0) {
427 // We failed this time and will try again in 3 seconds.
428 delayUs = 3000000ll;
429 } else {
430 delayUs = 0;
431 }
432 } else {
433 delayUs = 100000ll;
434 }
435
436 (new AMessage(kWhatFetchMore, mReflector))->post(delayUs);
437 }
438
onRead(const sp<AMessage> & msg)439 void NuCachedSource2::onRead(const sp<AMessage> &msg) {
440 ALOGV("onRead");
441
442 int64_t offset;
443 CHECK(msg->findInt64("offset", &offset));
444
445 void *data;
446 CHECK(msg->findPointer("data", &data));
447
448 size_t size;
449 CHECK(msg->findSize("size", &size));
450
451 ssize_t result = readInternal(offset, data, size);
452
453 if (result == -EAGAIN) {
454 msg->post(50000);
455 return;
456 }
457
458 Mutex::Autolock autoLock(mLock);
459 if (mDisconnecting) {
460 mCondition.signal();
461 return;
462 }
463
464 CHECK(mAsyncResult == NULL);
465
466 mAsyncResult = new AMessage;
467 mAsyncResult->setInt32("result", result);
468
469 mCondition.signal();
470 }
471
restartPrefetcherIfNecessary_l(bool ignoreLowWaterThreshold,bool force)472 void NuCachedSource2::restartPrefetcherIfNecessary_l(
473 bool ignoreLowWaterThreshold, bool force) {
474 static const size_t kGrayArea = 1024 * 1024;
475
476 if (mFetching || (mFinalStatus != OK && mNumRetriesLeft == 0)) {
477 return;
478 }
479
480 if (!ignoreLowWaterThreshold && !force
481 && mCacheOffset + mCache->totalSize() - mLastAccessPos
482 >= mLowwaterThresholdBytes) {
483 return;
484 }
485
486 size_t maxBytes = mLastAccessPos - mCacheOffset;
487
488 if (!force) {
489 if (maxBytes < kGrayArea) {
490 return;
491 }
492
493 maxBytes -= kGrayArea;
494 }
495
496 size_t actualBytes = mCache->releaseFromStart(maxBytes);
497 mCacheOffset += actualBytes;
498
499 ALOGI("restarting prefetcher, totalSize = %zu", mCache->totalSize());
500 mFetching = true;
501 }
502
readAt(off64_t offset,void * data,size_t size)503 ssize_t NuCachedSource2::readAt(off64_t offset, void *data, size_t size) {
504 Mutex::Autolock autoSerializer(mSerializer);
505
506 ALOGV("readAt offset %lld, size %zu", (long long)offset, size);
507
508 Mutex::Autolock autoLock(mLock);
509 if (mDisconnecting) {
510 return ERROR_END_OF_STREAM;
511 }
512
513 // If the request can be completely satisfied from the cache, do so.
514
515 if (offset >= mCacheOffset
516 && offset + size <= mCacheOffset + mCache->totalSize()) {
517 size_t delta = offset - mCacheOffset;
518 mCache->copy(delta, data, size);
519
520 mLastAccessPos = offset + size;
521
522 return size;
523 }
524
525 sp<AMessage> msg = new AMessage(kWhatRead, mReflector);
526 msg->setInt64("offset", offset);
527 msg->setPointer("data", data);
528 msg->setSize("size", size);
529
530 CHECK(mAsyncResult == NULL);
531 msg->post();
532
533 while (mAsyncResult == NULL && !mDisconnecting) {
534 mCondition.wait(mLock);
535 }
536
537 if (mDisconnecting) {
538 mAsyncResult.clear();
539 return ERROR_END_OF_STREAM;
540 }
541
542 int32_t result;
543 CHECK(mAsyncResult->findInt32("result", &result));
544
545 mAsyncResult.clear();
546
547 if (result > 0) {
548 mLastAccessPos = offset + result;
549 }
550
551 return (ssize_t)result;
552 }
553
cachedSize()554 size_t NuCachedSource2::cachedSize() {
555 Mutex::Autolock autoLock(mLock);
556 return mCacheOffset + mCache->totalSize();
557 }
558
approxDataRemaining(status_t * finalStatus) const559 size_t NuCachedSource2::approxDataRemaining(status_t *finalStatus) const {
560 Mutex::Autolock autoLock(mLock);
561 return approxDataRemaining_l(finalStatus);
562 }
563
approxDataRemaining_l(status_t * finalStatus) const564 size_t NuCachedSource2::approxDataRemaining_l(status_t *finalStatus) const {
565 *finalStatus = mFinalStatus;
566
567 if (mFinalStatus != OK && mNumRetriesLeft > 0) {
568 // Pretend that everything is fine until we're out of retries.
569 *finalStatus = OK;
570 }
571
572 off64_t lastBytePosCached = mCacheOffset + mCache->totalSize();
573 if (mLastAccessPos < lastBytePosCached) {
574 return lastBytePosCached - mLastAccessPos;
575 }
576 return 0;
577 }
578
readInternal(off64_t offset,void * data,size_t size)579 ssize_t NuCachedSource2::readInternal(off64_t offset, void *data, size_t size) {
580 CHECK_LE(size, (size_t)mHighwaterThresholdBytes);
581
582 ALOGV("readInternal offset %lld size %zu", (long long)offset, size);
583
584 Mutex::Autolock autoLock(mLock);
585
586 // If we're disconnecting, return EOS and don't access *data pointer.
587 // data could be on the stack of the caller to NuCachedSource2::readAt(),
588 // which may have exited already.
589 if (mDisconnecting) {
590 return ERROR_END_OF_STREAM;
591 }
592
593 if (!mFetching) {
594 mLastAccessPos = offset;
595 restartPrefetcherIfNecessary_l(
596 false, // ignoreLowWaterThreshold
597 true); // force
598 }
599
600 if (offset < mCacheOffset
601 || offset >= (off64_t)(mCacheOffset + mCache->totalSize())) {
602 static const off64_t kPadding = 256 * 1024;
603
604 // In the presence of multiple decoded streams, once of them will
605 // trigger this seek request, the other one will request data "nearby"
606 // soon, adjust the seek position so that that subsequent request
607 // does not trigger another seek.
608 off64_t seekOffset = (offset > kPadding) ? offset - kPadding : 0;
609
610 seekInternal_l(seekOffset);
611 }
612
613 size_t delta = offset - mCacheOffset;
614
615 if (mFinalStatus != OK && mNumRetriesLeft == 0) {
616 if (delta >= mCache->totalSize()) {
617 return mFinalStatus;
618 }
619
620 size_t avail = mCache->totalSize() - delta;
621
622 if (avail > size) {
623 avail = size;
624 }
625
626 mCache->copy(delta, data, avail);
627
628 return avail;
629 }
630
631 if (offset + size <= mCacheOffset + mCache->totalSize()) {
632 mCache->copy(delta, data, size);
633
634 return size;
635 }
636
637 ALOGV("deferring read");
638
639 return -EAGAIN;
640 }
641
seekInternal_l(off64_t offset)642 status_t NuCachedSource2::seekInternal_l(off64_t offset) {
643 mLastAccessPos = offset;
644
645 if (offset >= mCacheOffset
646 && offset <= (off64_t)(mCacheOffset + mCache->totalSize())) {
647 return OK;
648 }
649
650 ALOGI("new range: offset= %lld", (long long)offset);
651
652 mCacheOffset = offset;
653
654 size_t totalSize = mCache->totalSize();
655 CHECK_EQ(mCache->releaseFromStart(totalSize), totalSize);
656
657 mNumRetriesLeft = kMaxNumRetries;
658 mFetching = true;
659
660 return OK;
661 }
662
resumeFetchingIfNecessary()663 void NuCachedSource2::resumeFetchingIfNecessary() {
664 Mutex::Autolock autoLock(mLock);
665
666 restartPrefetcherIfNecessary_l(true /* ignore low water threshold */);
667 }
668
DrmInitialization(const char * mime)669 sp<DecryptHandle> NuCachedSource2::DrmInitialization(const char* mime) {
670 return mSource->DrmInitialization(mime);
671 }
672
getDrmInfo(sp<DecryptHandle> & handle,DrmManagerClient ** client)673 void NuCachedSource2::getDrmInfo(sp<DecryptHandle> &handle, DrmManagerClient **client) {
674 mSource->getDrmInfo(handle, client);
675 }
676
getUri()677 String8 NuCachedSource2::getUri() {
678 return mSource->getUri();
679 }
680
getMIMEType() const681 String8 NuCachedSource2::getMIMEType() const {
682 return mSource->getMIMEType();
683 }
684
updateCacheParamsFromSystemProperty()685 void NuCachedSource2::updateCacheParamsFromSystemProperty() {
686 char value[PROPERTY_VALUE_MAX];
687 if (!property_get("media.stagefright.cache-params", value, NULL)) {
688 return;
689 }
690
691 updateCacheParamsFromString(value);
692 }
693
updateCacheParamsFromString(const char * s)694 void NuCachedSource2::updateCacheParamsFromString(const char *s) {
695 ssize_t lowwaterMarkKb, highwaterMarkKb;
696 int keepAliveSecs;
697
698 if (sscanf(s, "%zd/%zd/%d",
699 &lowwaterMarkKb, &highwaterMarkKb, &keepAliveSecs) != 3) {
700 ALOGE("Failed to parse cache parameters from '%s'.", s);
701 return;
702 }
703
704 if (lowwaterMarkKb >= 0) {
705 mLowwaterThresholdBytes = lowwaterMarkKb * 1024;
706 } else {
707 mLowwaterThresholdBytes = kDefaultLowWaterThreshold;
708 }
709
710 if (highwaterMarkKb >= 0) {
711 mHighwaterThresholdBytes = highwaterMarkKb * 1024;
712 } else {
713 mHighwaterThresholdBytes = kDefaultHighWaterThreshold;
714 }
715
716 if (mLowwaterThresholdBytes >= mHighwaterThresholdBytes) {
717 ALOGE("Illegal low/highwater marks specified, reverting to defaults.");
718
719 mLowwaterThresholdBytes = kDefaultLowWaterThreshold;
720 mHighwaterThresholdBytes = kDefaultHighWaterThreshold;
721 }
722
723 if (keepAliveSecs >= 0) {
724 mKeepAliveIntervalUs = keepAliveSecs * 1000000ll;
725 } else {
726 mKeepAliveIntervalUs = kDefaultKeepAliveIntervalUs;
727 }
728
729 ALOGV("lowwater = %zu bytes, highwater = %zu bytes, keepalive = %lld us",
730 mLowwaterThresholdBytes,
731 mHighwaterThresholdBytes,
732 (long long)mKeepAliveIntervalUs);
733 }
734
735 // static
RemoveCacheSpecificHeaders(KeyedVector<String8,String8> * headers,String8 * cacheConfig,bool * disconnectAtHighwatermark)736 void NuCachedSource2::RemoveCacheSpecificHeaders(
737 KeyedVector<String8, String8> *headers,
738 String8 *cacheConfig,
739 bool *disconnectAtHighwatermark) {
740 *cacheConfig = String8();
741 *disconnectAtHighwatermark = false;
742
743 if (headers == NULL) {
744 return;
745 }
746
747 ssize_t index;
748 if ((index = headers->indexOfKey(String8("x-cache-config"))) >= 0) {
749 *cacheConfig = headers->valueAt(index);
750
751 headers->removeItemsAt(index);
752
753 ALOGV("Using special cache config '%s'", cacheConfig->string());
754 }
755
756 if ((index = headers->indexOfKey(
757 String8("x-disconnect-at-highwatermark"))) >= 0) {
758 *disconnectAtHighwatermark = true;
759 headers->removeItemsAt(index);
760
761 ALOGV("Client requested disconnection at highwater mark");
762 }
763 }
764
765 } // namespace android
766