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
81namespace 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)
88static const nsecs_t kNewSetIntervalNs = 3600*(1000*1000*1000ll);
89static const int kMaxRecordSets = 48;
90// individual records kept in memory
91static const int kMaxRecords    = 100;
92
93
94static 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
104void MediaAnalyticsService::instantiate() {
105    defaultServiceManager()->addService(
106            String16(kServiceName), new MediaAnalyticsService());
107}
108
109// handle sets of summarizers
110MediaAnalyticsService::SummarizerSet::SummarizerSet() {
111    mSummarizers = new List<MetricsSummarizer *>();
112}
113MediaAnalyticsService::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
123void 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
154MediaAnalyticsService::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
176MediaAnalyticsService::~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
189MediaAnalyticsItem::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'
197MediaAnalyticsItem::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
319status_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
427void 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
457void 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
487void 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...
505String8 MediaAnalyticsService::dumpQueue(List<MediaAnalyticsItem *> *theList) {
506    return dumpQueue(theList, (nsecs_t) 0, NULL);
507}
508
509String8 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
541void 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?
564static 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
600MediaAnalyticsItem *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
629void 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
643static AString allowedKeys[] =
644{
645    "codec",
646    "extractor"
647};
648
649static const int nAllowedKeys = sizeof(allowedKeys) / sizeof(allowedKeys[0]);
650
651// are the contents good
652bool 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
677bool MediaAnalyticsService::rateLimited(MediaAnalyticsItem *) {
678
679    return false;
680}
681
682// insert into the appropriate summarizer.
683// we make our own copy to save/summarize
684void 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