blob: a039c6caee26270db0325c7b2081e7ba494c223c [file] [log] [blame]
Ray Essick3938dc62016-11-01 08:56:56 -07001/*
2 * Copyright (C) 2016 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 <inttypes.h>
24#include <sys/types.h>
25#include <sys/stat.h>
26#include <sys/time.h>
27#include <dirent.h>
28#include <unistd.h>
29
30#include <string.h>
31
32#include <cutils/atomic.h>
33#include <cutils/properties.h> // for property_get
34
35#include <utils/misc.h>
36
37#include <binder/IPCThreadState.h>
38#include <binder/IServiceManager.h>
39#include <binder/MemoryHeapBase.h>
40#include <binder/MemoryBase.h>
41#include <gui/Surface.h>
42#include <utils/Errors.h> // for status_t
43#include <utils/List.h>
44#include <utils/String8.h>
45#include <utils/SystemClock.h>
46#include <utils/Timers.h>
47#include <utils/Vector.h>
48
49#include <media/AudioPolicyHelper.h>
50#include <media/IMediaHTTPService.h>
51#include <media/IRemoteDisplay.h>
52#include <media/IRemoteDisplayClient.h>
53#include <media/MediaPlayerInterface.h>
54#include <media/mediarecorder.h>
55#include <media/MediaMetadataRetrieverInterface.h>
56#include <media/Metadata.h>
57#include <media/AudioTrack.h>
58#include <media/MemoryLeakTrackUtil.h>
59#include <media/stagefright/MediaCodecList.h>
60#include <media/stagefright/MediaErrors.h>
61#include <media/stagefright/Utils.h>
62#include <media/stagefright/foundation/ADebug.h>
63#include <media/stagefright/foundation/ALooperRoster.h>
64#include <mediautils/BatteryNotifier.h>
65
66//#include <memunreachable/memunreachable.h>
67#include <system/audio.h>
68
69#include <private/android_filesystem_config.h>
70
71#include "MediaAnalyticsService.h"
72
73
74namespace android {
75
76
77static int trackqueue = 0;
78
79//using android::status_t;
80//using android::OK;
81//using android::BAD_VALUE;
82//using android::NOT_ENOUGH_DATA;
83//using android::Parcel;
84
85
86void MediaAnalyticsService::instantiate() {
87 defaultServiceManager()->addService(
88 String16("media.analytics"), new MediaAnalyticsService());
89}
90
91// XXX: add dynamic controls for mMaxRecords
92MediaAnalyticsService::MediaAnalyticsService()
93 : mMaxRecords(100) {
94
95 ALOGD("MediaAnalyticsService created");
96 // clear our queues
97 mOpen = new List<sp<MediaAnalyticsItem>>();
98 mFinalized = new List<sp<MediaAnalyticsItem>>();
99
100 mItemsSubmitted = 0;
101 mItemsFinalized = 0;
102 mItemsDiscarded = 0;
103
104 mLastSessionID = 0;
105 // recover any persistency we set up
106 // etc
107}
108
109MediaAnalyticsService::~MediaAnalyticsService() {
110 ALOGD("MediaAnalyticsService destroyed");
111
112 // XXX: clean out mOpen and mFinalized
113}
114
115
116MediaAnalyticsItem::SessionID_t MediaAnalyticsService::generateUniqueSessionID() {
117 // generate a new sessionid
118
119 Mutex::Autolock _l(mLock_ids);
120 return (++mLastSessionID);
121}
122
123MediaAnalyticsItem::SessionID_t MediaAnalyticsService::submit(sp<MediaAnalyticsItem> item, bool forcenew) {
124
125 MediaAnalyticsItem::SessionID_t id = MediaAnalyticsItem::SessionIDInvalid;
126
127 // we control these, not using whatever the user might have sent
128 nsecs_t now = systemTime(SYSTEM_TIME_REALTIME);
129 item->setTimestamp(now);
130 int pid = IPCThreadState::self()->getCallingPid();
131 item->setPid(pid);
132 int uid = IPCThreadState::self()->getCallingUid();
133 item->setUid(uid);
134
135 mItemsSubmitted++;
136
137 // validate the record; we discard if we don't like it
138 if (contentValid(item) == false) {
139 return MediaAnalyticsItem::SessionIDInvalid;
140 }
141
142
143 // if we have a sesisonid in the new record, look to make
144 // sure it doesn't appear in the finalized list.
145 // XXX: this is for security / DOS prevention.
146 // may also require that we persist the unique sessionIDs
147 // across boots [instead of within a single boot]
148
149
150 // match this new record up against records in the open
151 // list...
152 // if there's a match, merge them together
153 // deal with moving the old / merged record into the finalized que
154
155 bool finalizing = item->getFinalized();
156
157 // if finalizing, we'll remove it
158 sp<MediaAnalyticsItem> oitem = findItem(mOpen, item, finalizing | forcenew);
159 if (oitem != NULL) {
160 if (forcenew) {
161 // old one gets finalized, then we insert the new one
162 // so we'll have 2 records at the end of this.
163 // but don't finalize an empty record
164 if (oitem->count() != 0) {
165 oitem->setFinalized(true);
166 saveItem(mFinalized, oitem, 0);
167 }
168 // new record could itself be marked finalized...
169 if (finalizing) {
170 saveItem(mFinalized, item, 0);
171 mItemsFinalized++;
172 } else {
173 saveItem(mOpen, item, 1);
174 }
175 id = item->getSessionID();
176 } else {
177 // combine the records, send it to finalized if appropriate
178 oitem->merge(item);
179 if (finalizing) {
180 saveItem(mFinalized, oitem, 0);
181 mItemsFinalized++;
182 }
183 id = oitem->getSessionID();
184 }
185 } else {
186 // nothing to merge, save the new record
187 if (finalizing) {
188 if (item->count() != 0) {
189 // drop empty records
190 saveItem(mFinalized, item, 0);
191 mItemsFinalized++;
192 }
193 } else {
194 saveItem(mOpen, item, 1);
195 }
196 id = item->getSessionID();
197 }
198
199 return id;
200}
201
202List<sp<MediaAnalyticsItem>> *MediaAnalyticsService::getMediaAnalyticsItemList(bool finished, nsecs_t ts) {
203 // this might never get called; the binder interface maps to the full parm list
204 // on the client side before making the binder call.
205 // but this lets us be sure...
206 List<sp<MediaAnalyticsItem>> *list;
207 list = getMediaAnalyticsItemList(finished, ts, MediaAnalyticsItem::kKeyAny);
208 return list;
209}
210
211List<sp<MediaAnalyticsItem>> *MediaAnalyticsService::getMediaAnalyticsItemList(bool , nsecs_t , MediaAnalyticsItem::Key ) {
212
213 // XXX: implement the get-item-list semantics
214
215 List<sp<MediaAnalyticsItem>> *list = NULL;
216 // set up our query on the persistent data
217 // slurp in all of the pieces
218 // return that
219 return list;
220}
221
222// ignoring 2nd argument, name removed to keep compiler happy
223// XXX: arguments to parse:
224// -- a timestamp (either since X or last X seconds) to bound search
225status_t MediaAnalyticsService::dump(int fd, const Vector<String16>&)
226{
227 const size_t SIZE = 256;
228 char buffer[SIZE];
229 String8 result;
230
231 if (checkCallingPermission(String16("android.permission.DUMP")) == false) {
232 snprintf(buffer, SIZE, "Permission Denial: "
233 "can't dump MediaAnalyticsService from pid=%d, uid=%d\n",
234 IPCThreadState::self()->getCallingPid(),
235 IPCThreadState::self()->getCallingUid());
236 result.append(buffer);
237 } else {
238
239 // crack parameters
240
241
242 Mutex::Autolock _l(mLock);
243
244 snprintf(buffer, SIZE, "Dump of the mediaanalytics process:\n");
245 result.append(buffer);
246
247 int enabled = MediaAnalyticsItem::isEnabled();
248 if (enabled) {
249 snprintf(buffer, SIZE, "Analytics gathering: enabled\n");
250 } else {
251 snprintf(buffer, SIZE, "Analytics gathering: DISABLED via property\n");
252 }
253 result.append(buffer);
254
255 snprintf(buffer, SIZE,
256 "Since Boot: Submissions: %" PRId64
257 " Finalizations: %" PRId64
258 " Discarded: %" PRId64 "\n",
259 mItemsSubmitted, mItemsFinalized, mItemsDiscarded);
260 result.append(buffer);
261
262 // show the recently recorded records
263 snprintf(buffer, sizeof(buffer), "\nFinalized Analytics (oldest first):\n");
264 result.append(buffer);
265 result.append(this->dumpQueue(mFinalized));
266
267 snprintf(buffer, sizeof(buffer), "\nIn-Progress Analytics (newest first):\n");
268 result.append(buffer);
269 result.append(this->dumpQueue(mOpen));
270
271 // show who is connected and injecting records?
272 // talk about # records fed to the 'readers'
273 // talk about # records we discarded, perhaps "discarded w/o reading" too
274
275 }
276 write(fd, result.string(), result.size());
277 return NO_ERROR;
278}
279
280// caller has locked mLock...
281String8 MediaAnalyticsService::dumpQueue(List<sp<MediaAnalyticsItem>> *theList) {
282 const size_t SIZE = 256;
283 char buffer[SIZE];
284 String8 result;
285 int slot = 0;
286
287 if (theList->empty()) {
288 result.append("empty\n");
289 } else {
290 List<sp<MediaAnalyticsItem>>::iterator it = theList->begin();
291 for (; it != theList->end(); it++, slot++) {
292 AString entry = (*it)->toString();
293 snprintf(buffer, sizeof(buffer), "%4d: %s\n",
294 slot, entry.c_str());
295 result.append(buffer);
296 }
297 }
298
299 return result;
300}
301
302//
303// Our Cheap in-core, non-persistent records management.
304// XXX: rewrite this to manage persistence, etc.
305
306// insert appropriately into queue
307void MediaAnalyticsService::saveItem(List<sp<MediaAnalyticsItem>> *l, sp<MediaAnalyticsItem> item, int front) {
308
309 Mutex::Autolock _l(mLock);
310
311 if (false)
312 ALOGD("Inject a record: session %" PRId64 " ts %" PRId64 "",
313 item->getSessionID(), item->getTimestamp());
314
315 if (trackqueue) {
316 String8 before = dumpQueue(l);
317 ALOGD("Q before insert: %s", before.string());
318 }
319
320 // adding at back of queue (fifo order)
321 if (front) {
322 l->push_front(item);
323 } else {
324 l->push_back(item);
325 }
326
327 if (trackqueue) {
328 String8 after = dumpQueue(l);
329 ALOGD("Q after insert: %s", after.string());
330 }
331
332 // keep removing old records the front until we're in-bounds
333 if (mMaxRecords > 0) {
334 while (l->size() > (size_t) mMaxRecords) {
335 sp<MediaAnalyticsItem> oitem = *(l->begin());
336 if (trackqueue) {
337 ALOGD("zap old record: key %s sessionID %" PRId64 " ts %" PRId64 "",
338 oitem->getKey().c_str(), oitem->getSessionID(),
339 oitem->getTimestamp());
340 }
341 l->erase(l->begin());
342 mItemsDiscarded++;
343 }
344 }
345
346 if (trackqueue) {
347 String8 after = dumpQueue(l);
348 ALOGD("Q after cleanup: %s", after.string());
349 }
350}
351
352// are they alike enough that nitem can be folded into oitem?
353static bool compatibleItems(sp<MediaAnalyticsItem> oitem, sp<MediaAnalyticsItem> nitem) {
354
355 if (0) {
356 ALOGD("Compare: o %s n %s",
357 oitem->toString().c_str(), nitem->toString().c_str());
358 }
359
360 // general safety
361 if (nitem->getUid() != oitem->getUid()) {
362 return false;
363 }
364 if (nitem->getPid() != oitem->getPid()) {
365 return false;
366 }
367
368 // key -- needs to match
369 if (nitem->getKey() == oitem->getKey()) {
370 // still in the game.
371 } else {
372 return false;
373 }
374
375 // session id -- empty field in new is allowed
376 MediaAnalyticsItem::SessionID_t osession = oitem->getSessionID();
377 MediaAnalyticsItem::SessionID_t nsession = nitem->getSessionID();
378 if (nsession != osession) {
379 // incoming '0' matches value in osession
380 if (nsession != 0) {
381 return false;
382 }
383 }
384
385 return true;
386}
387
388// find the incomplete record that this will overlay
389sp<MediaAnalyticsItem> MediaAnalyticsService::findItem(List<sp<MediaAnalyticsItem>> *theList, sp<MediaAnalyticsItem> nitem, bool removeit) {
390 sp<MediaAnalyticsItem> item;
391
392 if (nitem == NULL) {
393 return NULL;
394 }
395
396 Mutex::Autolock _l(mLock);
397
398 for (List<sp<MediaAnalyticsItem>>::iterator it = theList->begin();
399 it != theList->end(); it++) {
400 sp<MediaAnalyticsItem> tmp = (*it);
401
402 if (!compatibleItems(tmp, nitem)) {
403 continue;
404 }
405
406 // we match! this is the one I want.
407 if (removeit) {
408 theList->erase(it);
409 }
410 item = tmp;
411 break;
412 }
413 return item;
414}
415
416
417// delete the indicated record
418void MediaAnalyticsService::deleteItem(List<sp<MediaAnalyticsItem>> *l, sp<MediaAnalyticsItem> item) {
419
420 Mutex::Autolock _l(mLock);
421
422 if(trackqueue) {
423 String8 before = dumpQueue(l);
424 ALOGD("Q before delete: %s", before.string());
425 }
426
427 for (List<sp<MediaAnalyticsItem>>::iterator it = l->begin();
428 it != l->end(); it++) {
429 if ((*it)->getSessionID() != item->getSessionID())
430 continue;
431
432 ALOGD(" --- removing record for SessionID %" PRId64 "", item->getSessionID());
433 l->erase(it);
434 break;
435 }
436
437 if (trackqueue) {
438 String8 after = dumpQueue(l);
439 ALOGD("Q after delete: %s", after.string());
440 }
441}
442
443// are the contents good
444bool MediaAnalyticsService::contentValid(sp<MediaAnalyticsItem>) {
445
446 // certain keys require certain uids
447 // internal consistency
448
449 return true;
450}
451
452// are we rate limited, normally false
453bool MediaAnalyticsService::rateLimited(sp<MediaAnalyticsItem>) {
454
455 return false;
456}
457
458
459} // namespace android