• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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 // Proxy for media player implementations
18 
19 //#define LOG_NDEBUG 0
20 #define LOG_TAG "MediaAnalyticsService"
21 #include <utils/Log.h>
22 
23 #include <stdint.h>
24 #include <inttypes.h>
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <sys/time.h>
28 #include <dirent.h>
29 #include <unistd.h>
30 
31 #include <string.h>
32 
33 #include <cutils/atomic.h>
34 #include <cutils/properties.h> // for property_get
35 
36 #include <utils/misc.h>
37 
38 #include <binder/IPCThreadState.h>
39 #include <binder/IServiceManager.h>
40 #include <binder/MemoryHeapBase.h>
41 #include <binder/MemoryBase.h>
42 #include <gui/Surface.h>
43 #include <utils/Errors.h>  // for status_t
44 #include <utils/List.h>
45 #include <utils/String8.h>
46 #include <utils/SystemClock.h>
47 #include <utils/Timers.h>
48 #include <utils/Vector.h>
49 
50 #include <media/AudioPolicyHelper.h>
51 #include <media/IMediaHTTPService.h>
52 #include <media/IRemoteDisplay.h>
53 #include <media/IRemoteDisplayClient.h>
54 #include <media/MediaPlayerInterface.h>
55 #include <media/mediarecorder.h>
56 #include <media/MediaMetadataRetrieverInterface.h>
57 #include <media/Metadata.h>
58 #include <media/AudioTrack.h>
59 #include <media/MemoryLeakTrackUtil.h>
60 #include <media/stagefright/MediaCodecList.h>
61 #include <media/stagefright/MediaErrors.h>
62 #include <media/stagefright/Utils.h>
63 #include <media/stagefright/foundation/ADebug.h>
64 #include <media/stagefright/foundation/ALooperRoster.h>
65 #include <mediautils/BatteryNotifier.h>
66 
67 //#include <memunreachable/memunreachable.h>
68 #include <system/audio.h>
69 
70 #include <private/android_filesystem_config.h>
71 
72 #include "MediaAnalyticsService.h"
73 
74 #include "MetricsSummarizer.h"
75 #include "MetricsSummarizerCodec.h"
76 #include "MetricsSummarizerExtractor.h"
77 #include "MetricsSummarizerPlayer.h"
78 #include "MetricsSummarizerRecorder.h"
79 
80 
81 namespace android {
82 
83 
84 
85 // summarized records
86 // up to 48 sets, each covering an hour -- at least 2 days of coverage
87 // (will be longer if there are hours without any media action)
88 static const nsecs_t kNewSetIntervalNs = 3600*(1000*1000*1000ll);
89 static const int kMaxRecordSets = 48;
90 // individual records kept in memory
91 static const int kMaxRecords    = 100;
92 
93 
94 static const char *kServiceName = "media.metrics";
95 
96 
97 //using android::status_t;
98 //using android::OK;
99 //using android::BAD_VALUE;
100 //using android::NOT_ENOUGH_DATA;
101 //using android::Parcel;
102 
103 
instantiate()104 void MediaAnalyticsService::instantiate() {
105     defaultServiceManager()->addService(
106             String16(kServiceName), new MediaAnalyticsService());
107 }
108 
109 // handle sets of summarizers
SummarizerSet()110 MediaAnalyticsService::SummarizerSet::SummarizerSet() {
111     mSummarizers = new List<MetricsSummarizer *>();
112 }
~SummarizerSet()113 MediaAnalyticsService::SummarizerSet::~SummarizerSet() {
114     // empty the list
115     List<MetricsSummarizer *> *l = mSummarizers;
116     while (l->size() > 0) {
117         MetricsSummarizer *summarizer = *(l->begin());
118         l->erase(l->begin());
119         delete summarizer;
120     }
121 }
122 
newSummarizerSet()123 void MediaAnalyticsService::newSummarizerSet() {
124     ALOGD("MediaAnalyticsService::newSummarizerSet");
125     MediaAnalyticsService::SummarizerSet *set = new MediaAnalyticsService::SummarizerSet();
126     nsecs_t now = systemTime(SYSTEM_TIME_REALTIME);
127     set->setStarted(now);
128 
129     set->appendSummarizer(new MetricsSummarizerExtractor("extractor"));
130     set->appendSummarizer(new MetricsSummarizerCodec("codec"));
131     set->appendSummarizer(new MetricsSummarizerPlayer("nuplayer"));
132     set->appendSummarizer(new MetricsSummarizerRecorder("recorder"));
133 
134     // ALWAYS at the end, since it catches everything
135     set->appendSummarizer(new MetricsSummarizer(NULL));
136 
137     // inject this set at the BACK of the list.
138     mSummarizerSets->push_back(set);
139     mCurrentSet = set;
140 
141     // limit the # that we have
142     if (mMaxRecordSets > 0) {
143         List<SummarizerSet *> *l = mSummarizerSets;
144         while (l->size() > (size_t) mMaxRecordSets) {
145             ALOGD("Deleting oldest record set....");
146             MediaAnalyticsService::SummarizerSet *oset = *(l->begin());
147             l->erase(l->begin());
148             delete oset;
149             mSetsDiscarded++;
150         }
151     }
152 }
153 
MediaAnalyticsService()154 MediaAnalyticsService::MediaAnalyticsService()
155         : mMaxRecords(kMaxRecords),
156           mMaxRecordSets(kMaxRecordSets),
157           mNewSetInterval(kNewSetIntervalNs) {
158 
159     ALOGD("MediaAnalyticsService created");
160     // clear our queues
161     mOpen = new List<MediaAnalyticsItem *>();
162     mFinalized = new List<MediaAnalyticsItem *>();
163 
164     mSummarizerSets = new List<MediaAnalyticsService::SummarizerSet *>();
165     newSummarizerSet();
166 
167     mItemsSubmitted = 0;
168     mItemsFinalized = 0;
169     mItemsDiscarded = 0;
170 
171     mLastSessionID = 0;
172     // recover any persistency we set up
173     // etc
174 }
175 
~MediaAnalyticsService()176 MediaAnalyticsService::~MediaAnalyticsService() {
177         ALOGD("MediaAnalyticsService destroyed");
178 
179     // clean out mOpen and mFinalized
180     delete mOpen;
181     mOpen = NULL;
182     delete mFinalized;
183     mFinalized = NULL;
184 
185     // XXX: clean out the summaries
186 }
187 
188 
generateUniqueSessionID()189 MediaAnalyticsItem::SessionID_t MediaAnalyticsService::generateUniqueSessionID() {
190     // generate a new sessionid
191 
192     Mutex::Autolock _l(mLock_ids);
193     return (++mLastSessionID);
194 }
195 
196 // caller surrenders ownership of 'item'
submit(MediaAnalyticsItem * item,bool forcenew)197 MediaAnalyticsItem::SessionID_t MediaAnalyticsService::submit(MediaAnalyticsItem *item, bool forcenew) {
198 
199     MediaAnalyticsItem::SessionID_t id = MediaAnalyticsItem::SessionIDInvalid;
200 
201     // we control these, generally not trusting user input
202     nsecs_t now = systemTime(SYSTEM_TIME_REALTIME);
203     item->setTimestamp(now);
204     int pid = IPCThreadState::self()->getCallingPid();
205     int uid = IPCThreadState::self()->getCallingUid();
206 
207     int uid_given = item->getUid();
208     int pid_given = item->getPid();
209 
210     // although we do make exceptions for particular client uids
211     // that we know we trust.
212     //
213     bool isTrusted = false;
214 
215     switch (uid)  {
216         case AID_MEDIA:
217         case AID_MEDIA_CODEC:
218         case AID_MEDIA_EX:
219         case AID_MEDIA_DRM:
220             // trusted source, only override default values
221                 isTrusted = true;
222             if (uid_given == (-1)) {
223                 item->setUid(uid);
224             }
225             if (pid_given == (-1)) {
226                 item->setPid(pid);
227             }
228             break;
229         default:
230             isTrusted = false;
231             item->setPid(pid);
232             item->setUid(uid);
233             break;
234     }
235 
236 
237     mItemsSubmitted++;
238 
239     // validate the record; we discard if we don't like it
240     if (contentValid(item, isTrusted) == false) {
241         delete item;
242         return MediaAnalyticsItem::SessionIDInvalid;
243     }
244 
245 
246     // if we have a sesisonid in the new record, look to make
247     // sure it doesn't appear in the finalized list.
248     // XXX: this is for security / DOS prevention.
249     // may also require that we persist the unique sessionIDs
250     // across boots [instead of within a single boot]
251 
252 
253     // match this new record up against records in the open
254     // list...
255     // if there's a match, merge them together
256     // deal with moving the old / merged record into the finalized que
257 
258     bool finalizing = item->getFinalized();
259 
260     // if finalizing, we'll remove it
261     MediaAnalyticsItem *oitem = findItem(mOpen, item, finalizing | forcenew);
262     if (oitem != NULL) {
263         if (forcenew) {
264             // old one gets finalized, then we insert the new one
265             // so we'll have 2 records at the end of this.
266             // but don't finalize an empty record
267             if (oitem->count() == 0) {
268                 // we're responsible for disposing of the dead record
269                 delete oitem;
270                 oitem = NULL;
271             } else {
272                 oitem->setFinalized(true);
273                 summarize(oitem);
274                 saveItem(mFinalized, oitem, 0);
275             }
276             // new record could itself be marked finalized...
277             if (finalizing) {
278                 summarize(item);
279                 saveItem(mFinalized, item, 0);
280                 mItemsFinalized++;
281             } else {
282                 saveItem(mOpen, item, 1);
283             }
284             id = item->getSessionID();
285         } else {
286             // combine the records, send it to finalized if appropriate
287             oitem->merge(item);
288             if (finalizing) {
289                 summarize(oitem);
290                 saveItem(mFinalized, oitem, 0);
291                 mItemsFinalized++;
292             }
293             id = oitem->getSessionID();
294 
295             // we're responsible for disposing of the dead record
296             delete item;
297             item = NULL;
298         }
299     } else {
300         // nothing to merge, save the new record
301         id = item->getSessionID();
302         if (finalizing) {
303             if (item->count() == 0) {
304                 // drop empty records
305                 delete item;
306                 item = NULL;
307             } else {
308                 summarize(item);
309                 saveItem(mFinalized, item, 0);
310                 mItemsFinalized++;
311             }
312         } else {
313             saveItem(mOpen, item, 1);
314         }
315     }
316     return id;
317 }
318 
dump(int fd,const Vector<String16> & args)319 status_t MediaAnalyticsService::dump(int fd, const Vector<String16>& args)
320 {
321     const size_t SIZE = 512;
322     char buffer[SIZE];
323     String8 result;
324 
325     if (checkCallingPermission(String16("android.permission.DUMP")) == false) {
326         snprintf(buffer, SIZE, "Permission Denial: "
327                 "can't dump MediaAnalyticsService from pid=%d, uid=%d\n",
328                 IPCThreadState::self()->getCallingPid(),
329                 IPCThreadState::self()->getCallingUid());
330         result.append(buffer);
331         write(fd, result.string(), result.size());
332         return NO_ERROR;
333     }
334 
335     // crack any parameters
336     bool clear = false;
337     bool summary = false;
338     nsecs_t ts_since = 0;
339     String16 summaryOption("-summary");
340     String16 clearOption("-clear");
341     String16 sinceOption("-since");
342     String16 helpOption("-help");
343     String16 onlyOption("-only");
344     const char *only = NULL;
345     int n = args.size();
346     for (int i = 0; i < n; i++) {
347         String8 myarg(args[i]);
348         if (args[i] == clearOption) {
349             clear = true;
350         } else if (args[i] == summaryOption) {
351             summary = true;
352         } else if (args[i] == sinceOption) {
353             i++;
354             if (i < n) {
355                 String8 value(args[i]);
356                 char *endp;
357                 const char *p = value.string();
358                 ts_since = strtoll(p, &endp, 10);
359                 if (endp == p || *endp != '\0') {
360                     ts_since = 0;
361                 }
362             } else {
363                 ts_since = 0;
364             }
365             // command line is milliseconds; internal units are nano-seconds
366             ts_since *= 1000*1000;
367         } else if (args[i] == onlyOption) {
368             i++;
369             if (i < n) {
370                 String8 value(args[i]);
371                 const char *p = value.string();
372                 char *q = strdup(p);
373                 if (q != NULL) {
374                     if (only != NULL) {
375                         free((void*)only);
376                     }
377                 only = q;
378                 }
379             }
380         } else if (args[i] == helpOption) {
381             result.append("Recognized parameters:\n");
382             result.append("-help        this help message\n");
383             result.append("-summary     show summary info\n");
384             result.append("-clear       clears out saved records\n");
385             result.append("-only X      process records for component X\n");
386             result.append("-since X     include records since X\n");
387             result.append("             (X is milliseconds since the UNIX epoch)\n");
388             write(fd, result.string(), result.size());
389             return NO_ERROR;
390         }
391     }
392 
393     Mutex::Autolock _l(mLock);
394 
395     // we ALWAYS dump this piece
396     snprintf(buffer, SIZE, "Dump of the %s process:\n", kServiceName);
397     result.append(buffer);
398 
399     dumpHeaders(result, ts_since);
400 
401     // only want 1, to avoid confusing folks that parse the output
402     if (summary) {
403         dumpSummaries(result, ts_since, only);
404     } else {
405         dumpRecent(result, ts_since, only);
406     }
407 
408 
409     if (clear) {
410         // remove everything from the finalized queue
411         while (mFinalized->size() > 0) {
412             MediaAnalyticsItem * oitem = *(mFinalized->begin());
413             mFinalized->erase(mFinalized->begin());
414             delete oitem;
415             mItemsDiscarded++;
416         }
417 
418         // shall we clear the summary data too?
419 
420     }
421 
422     write(fd, result.string(), result.size());
423     return NO_ERROR;
424 }
425 
426 // dump headers
dumpHeaders(String8 & result,nsecs_t ts_since)427 void MediaAnalyticsService::dumpHeaders(String8 &result, nsecs_t ts_since) {
428     const size_t SIZE = 512;
429     char buffer[SIZE];
430 
431     int enabled = MediaAnalyticsItem::isEnabled();
432     if (enabled) {
433         snprintf(buffer, SIZE, "Metrics gathering: enabled\n");
434     } else {
435         snprintf(buffer, SIZE, "Metrics gathering: DISABLED via property\n");
436     }
437     result.append(buffer);
438 
439     snprintf(buffer, SIZE,
440         "Since Boot: Submissions: %" PRId64
441             " Finalizations: %" PRId64
442         " Discarded: %" PRId64 "\n",
443         mItemsSubmitted, mItemsFinalized, mItemsDiscarded);
444     result.append(buffer);
445     snprintf(buffer, SIZE,
446         "Summary Sets Discarded: %" PRId64 "\n", mSetsDiscarded);
447     result.append(buffer);
448     if (ts_since != 0) {
449         snprintf(buffer, SIZE,
450             "Dumping Queue entries more recent than: %" PRId64 "\n",
451             (int64_t) ts_since);
452         result.append(buffer);
453     }
454 }
455 
456 // dump summary info
dumpSummaries(String8 & result,nsecs_t ts_since,const char * only)457 void MediaAnalyticsService::dumpSummaries(String8 &result, nsecs_t ts_since, const char *only) {
458     const size_t SIZE = 512;
459     char buffer[SIZE];
460     int slot = 0;
461 
462     snprintf(buffer, SIZE, "\nSummarized Metrics:\n");
463     result.append(buffer);
464 
465     // have each of the distillers dump records
466     if (mSummarizerSets != NULL) {
467         List<SummarizerSet *>::iterator itSet = mSummarizerSets->begin();
468         for (; itSet != mSummarizerSets->end(); itSet++) {
469             nsecs_t when = (*itSet)->getStarted();
470             if (when < ts_since) {
471                 continue;
472             }
473             List<MetricsSummarizer *> *list = (*itSet)->getSummarizers();
474             List<MetricsSummarizer *>::iterator it = list->begin();
475             for (; it != list->end(); it++) {
476                 if (only != NULL && strcmp(only, (*it)->getKey()) != 0) {
477                     ALOGV("Told to omit '%s'", (*it)->getKey());
478                 }
479                 AString distilled = (*it)->dumpSummary(slot, only);
480                 result.append(distilled.c_str());
481             }
482         }
483     }
484 }
485 
486 // the recent, detailed queues
dumpRecent(String8 & result,nsecs_t ts_since,const char * only)487 void MediaAnalyticsService::dumpRecent(String8 &result, nsecs_t ts_since, const char * only) {
488     const size_t SIZE = 512;
489     char buffer[SIZE];
490 
491     // show the recently recorded records
492     snprintf(buffer, sizeof(buffer), "\nFinalized Metrics (oldest first):\n");
493     result.append(buffer);
494     result.append(this->dumpQueue(mFinalized, ts_since, only));
495 
496     snprintf(buffer, sizeof(buffer), "\nIn-Progress Metrics (newest first):\n");
497     result.append(buffer);
498     result.append(this->dumpQueue(mOpen, ts_since, only));
499 
500     // show who is connected and injecting records?
501     // talk about # records fed to the 'readers'
502     // talk about # records we discarded, perhaps "discarded w/o reading" too
503 }
504 // caller has locked mLock...
dumpQueue(List<MediaAnalyticsItem * > * theList)505 String8 MediaAnalyticsService::dumpQueue(List<MediaAnalyticsItem *> *theList) {
506     return dumpQueue(theList, (nsecs_t) 0, NULL);
507 }
508 
dumpQueue(List<MediaAnalyticsItem * > * theList,nsecs_t ts_since,const char * only)509 String8 MediaAnalyticsService::dumpQueue(List<MediaAnalyticsItem *> *theList, nsecs_t ts_since, const char * only) {
510     String8 result;
511     int slot = 0;
512 
513     if (theList->empty()) {
514             result.append("empty\n");
515     } else {
516         List<MediaAnalyticsItem *>::iterator it = theList->begin();
517         for (; it != theList->end(); it++) {
518             nsecs_t when = (*it)->getTimestamp();
519             if (when < ts_since) {
520                 continue;
521             }
522             if (only != NULL &&
523                 strcmp(only, (*it)->getKey().c_str()) != 0) {
524                 ALOGV("Omit '%s', it's not '%s'", (*it)->getKey().c_str(), only);
525                 continue;
526             }
527             AString entry = (*it)->toString();
528             result.appendFormat("%5d: %s\n", slot, entry.c_str());
529             slot++;
530         }
531     }
532 
533     return result;
534 }
535 
536 //
537 // Our Cheap in-core, non-persistent records management.
538 // XXX: rewrite this to manage persistence, etc.
539 
540 // insert appropriately into queue
saveItem(List<MediaAnalyticsItem * > * l,MediaAnalyticsItem * item,int front)541 void MediaAnalyticsService::saveItem(List<MediaAnalyticsItem *> *l, MediaAnalyticsItem * item, int front) {
542 
543     Mutex::Autolock _l(mLock);
544 
545     // adding at back of queue (fifo order)
546     if (front)  {
547         l->push_front(item);
548     } else {
549         l->push_back(item);
550     }
551 
552     // keep removing old records the front until we're in-bounds
553     if (mMaxRecords > 0) {
554         while (l->size() > (size_t) mMaxRecords) {
555             MediaAnalyticsItem * oitem = *(l->begin());
556             l->erase(l->begin());
557             delete oitem;
558             mItemsDiscarded++;
559         }
560     }
561 }
562 
563 // are they alike enough that nitem can be folded into oitem?
compatibleItems(MediaAnalyticsItem * oitem,MediaAnalyticsItem * nitem)564 static bool compatibleItems(MediaAnalyticsItem * oitem, MediaAnalyticsItem * nitem) {
565 
566     if (0) {
567         ALOGD("Compare: o %s n %s",
568               oitem->toString().c_str(), nitem->toString().c_str());
569     }
570 
571     // general safety
572     if (nitem->getUid() != oitem->getUid()) {
573         return false;
574     }
575     if (nitem->getPid() != oitem->getPid()) {
576         return false;
577     }
578 
579     // key -- needs to match
580     if (nitem->getKey() == oitem->getKey()) {
581         // still in the game.
582     } else {
583         return false;
584     }
585 
586     // session id -- empty field in new is allowed
587     MediaAnalyticsItem::SessionID_t osession = oitem->getSessionID();
588     MediaAnalyticsItem::SessionID_t nsession = nitem->getSessionID();
589     if (nsession != osession) {
590         // incoming '0' matches value in osession
591         if (nsession != 0) {
592             return false;
593         }
594     }
595 
596     return true;
597 }
598 
599 // find the incomplete record that this will overlay
findItem(List<MediaAnalyticsItem * > * theList,MediaAnalyticsItem * nitem,bool removeit)600 MediaAnalyticsItem *MediaAnalyticsService::findItem(List<MediaAnalyticsItem*> *theList, MediaAnalyticsItem *nitem, bool removeit) {
601     if (nitem == NULL) {
602         return NULL;
603     }
604 
605     MediaAnalyticsItem *item = NULL;
606 
607     Mutex::Autolock _l(mLock);
608 
609     for (List<MediaAnalyticsItem *>::iterator it = theList->begin();
610         it != theList->end(); it++) {
611         MediaAnalyticsItem *tmp = (*it);
612 
613         if (!compatibleItems(tmp, nitem)) {
614             continue;
615         }
616 
617         // we match! this is the one I want.
618         if (removeit) {
619             theList->erase(it);
620         }
621         item = tmp;
622         break;
623     }
624     return item;
625 }
626 
627 
628 // delete the indicated record
deleteItem(List<MediaAnalyticsItem * > * l,MediaAnalyticsItem * item)629 void MediaAnalyticsService::deleteItem(List<MediaAnalyticsItem *> *l, MediaAnalyticsItem *item) {
630 
631     Mutex::Autolock _l(mLock);
632 
633     for (List<MediaAnalyticsItem *>::iterator it = l->begin();
634         it != l->end(); it++) {
635         if ((*it)->getSessionID() != item->getSessionID())
636             continue;
637         delete *it;
638         l->erase(it);
639         break;
640     }
641 }
642 
643 static AString allowedKeys[] =
644 {
645     "codec",
646     "extractor"
647 };
648 
649 static const int nAllowedKeys = sizeof(allowedKeys) / sizeof(allowedKeys[0]);
650 
651 // are the contents good
contentValid(MediaAnalyticsItem * item,bool isTrusted)652 bool MediaAnalyticsService::contentValid(MediaAnalyticsItem *item, bool isTrusted) {
653 
654     // untrusted uids can only send us a limited set of keys
655     if (isTrusted == false) {
656         // restrict to a specific set of keys
657         AString key = item->getKey();
658 
659         size_t i;
660         for(i = 0; i < nAllowedKeys; i++) {
661             if (key == allowedKeys[i]) {
662                 break;
663             }
664         }
665         if (i == nAllowedKeys) {
666             ALOGD("Ignoring (key): %s", item->toString().c_str());
667             return false;
668         }
669     }
670 
671     // internal consistency
672 
673     return true;
674 }
675 
676 // are we rate limited, normally false
rateLimited(MediaAnalyticsItem *)677 bool MediaAnalyticsService::rateLimited(MediaAnalyticsItem *) {
678 
679     return false;
680 }
681 
682 // insert into the appropriate summarizer.
683 // we make our own copy to save/summarize
summarize(MediaAnalyticsItem * item)684 void MediaAnalyticsService::summarize(MediaAnalyticsItem *item) {
685 
686     ALOGV("MediaAnalyticsService::summarize()");
687 
688     if (item == NULL) {
689         return;
690     }
691 
692     nsecs_t now = systemTime(SYSTEM_TIME_REALTIME);
693     if (mCurrentSet == NULL
694         || (mCurrentSet->getStarted() + mNewSetInterval < now)) {
695         newSummarizerSet();
696     }
697 
698     if (mCurrentSet == NULL) {
699         return;
700     }
701 
702     List<MetricsSummarizer *> *summarizers = mCurrentSet->getSummarizers();
703     List<MetricsSummarizer *>::iterator it = summarizers->begin();
704     for (; it != summarizers->end(); it++) {
705         if ((*it)->isMine(*item)) {
706             break;
707         }
708     }
709     if (it == summarizers->end()) {
710         ALOGD("no handler for type %s", item->getKey().c_str());
711         return;               // no handler
712     }
713 
714     // invoke the summarizer. summarizer will make whatever copies
715     // it wants; the caller retains ownership of item.
716 
717     (*it)->handleRecord(item);
718 
719 }
720 
721 } // namespace android
722