blob: 5f05d5a684f2528e5f03be2edc85180b7cd513c7 [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#undef LOG_TAG
18#define LOG_TAG "MediaAnalyticsItem"
19
20#include <sys/types.h>
21#include <inttypes.h>
22
23#include <binder/Parcel.h>
24#include <utils/Errors.h>
25#include <utils/Log.h>
26#include <utils/Mutex.h>
27#include <utils/RefBase.h>
28#include <utils/SortedVector.h>
29#include <utils/threads.h>
30
31#include <media/stagefright/foundation/AString.h>
32
33#include <binder/IServiceManager.h>
34#include <media/IMediaAnalyticsService.h>
35#include <media/MediaAnalyticsItem.h>
36
37namespace android {
38
39#define DEBUG_SERVICEACCESS 0
40
41// the few universal keys we have
42const MediaAnalyticsItem::Key MediaAnalyticsItem::kKeyAny = "any";
43const MediaAnalyticsItem::Key MediaAnalyticsItem::kKeyNone = "none";
44
45const char * const MediaAnalyticsItem::EnabledProperty = "media.analytics.enabled";
46const char * const MediaAnalyticsItem::EnabledPropertyPersist = "persist.media.analytics.enabled";
47const int MediaAnalyticsItem::EnabledProperty_default = 0;
48
49
50// access functions for the class
51MediaAnalyticsItem::MediaAnalyticsItem()
52 : RefBase(),
53 mPid(0),
54 mUid(0),
55 mSessionID(MediaAnalyticsItem::SessionIDNone),
56 mTimestamp(0),
57 mFinalized(0) {
58 mKey = MediaAnalyticsItem::kKeyNone;
59}
60
61MediaAnalyticsItem::MediaAnalyticsItem(MediaAnalyticsItem::Key key)
62 : RefBase(),
63 mPid(0),
64 mUid(0),
65 mSessionID(MediaAnalyticsItem::SessionIDNone),
66 mTimestamp(0),
67 mFinalized(0) {
68 mKey = key;
69}
70
71MediaAnalyticsItem::~MediaAnalyticsItem() {
72 clear();
73}
74
75// so clients can send intermediate values to be overlaid later
76MediaAnalyticsItem &MediaAnalyticsItem::setFinalized(bool value) {
77 mFinalized = value;
78 return *this;
79}
80
81bool MediaAnalyticsItem::getFinalized() const {
82 return mFinalized;
83}
84
85MediaAnalyticsItem &MediaAnalyticsItem::setSessionID(MediaAnalyticsItem::SessionID_t id) {
86 mSessionID = id;
87 return *this;
88}
89
90MediaAnalyticsItem::SessionID_t MediaAnalyticsItem::getSessionID() const {
91 return mSessionID;
92}
93
94MediaAnalyticsItem::SessionID_t MediaAnalyticsItem::generateSessionID() {
95 MediaAnalyticsItem::SessionID_t newid = SessionIDNone;
96 ALOGD("generateSessionID()");
97
98 if (mSessionID == SessionIDNone) {
99 // get one from the server
100 sp<IMediaAnalyticsService> svc = getInstance();
101 if (svc != NULL) {
102 newid = svc->generateUniqueSessionID();
103 }
104 mSessionID = newid;
105 }
106
107 return mSessionID;
108}
109
110MediaAnalyticsItem &MediaAnalyticsItem::clearSessionID() {
111 mSessionID = MediaAnalyticsItem::SessionIDNone;
112 return *this;
113}
114
115MediaAnalyticsItem &MediaAnalyticsItem::setTimestamp(nsecs_t ts) {
116 mTimestamp = ts;
117 return *this;
118}
119
120nsecs_t MediaAnalyticsItem::getTimestamp() const {
121 return mTimestamp;
122}
123
124MediaAnalyticsItem &MediaAnalyticsItem::setPid(pid_t pid) {
125 mPid = pid;
126 return *this;
127}
128
129pid_t MediaAnalyticsItem::getPid() const {
130 return mPid;
131}
132
133MediaAnalyticsItem &MediaAnalyticsItem::setUid(uid_t uid) {
134 mUid = uid;
135 return *this;
136}
137
138uid_t MediaAnalyticsItem::getUid() const {
139 return mUid;
140}
141
142void MediaAnalyticsItem::clear() {
143
144 mKey.clear();
145
146#if 0
147 // not sure that I need to (or should) be doing this...
148 // seeing some strangeness in some records
149 int count = mItems.size();
150 for (int i = 0 ; i < count; i++ ) {
151 MediaAnalyticsItem::Attr attr = mItems.keyAt(i);
152 const sp<Item> value = mItems.valueAt(i);
153 value->clear();
154 attr.clear();
155 }
156 mItems.clear();
157#endif
158
159 return;
160}
161
162// this key is for the overall record -- "vid" or "aud"
163// assuming for the moment we use int32_t like the
164// media frameworks MetaData.cpp
165MediaAnalyticsItem &MediaAnalyticsItem::setKey(MediaAnalyticsItem::Key key) {
166 // XXX: possible validation of legal keys.
167 mKey = key;
168 return *this;
169}
170
171MediaAnalyticsItem::Key MediaAnalyticsItem::getKey() {
172 return mKey;
173}
174
175// number of keys we have in our dictionary
176// we won't upload empty records
177int32_t MediaAnalyticsItem::count() const {
178 return mItems.size();
179}
180
181// set the values
182bool MediaAnalyticsItem::setInt32(MediaAnalyticsItem::Attr attr, int32_t value) {
183 ssize_t i = mItems.indexOfKey(attr);
184 bool overwrote = true;
185 if (i<0) {
186 sp<Item> item = new Item();
187 i = mItems.add(attr, item);
188 overwrote = false;
189 }
190 sp<Item> &item = mItems.editValueAt(i);
191 item->mType = MediaAnalyticsItem::Item::kTypeInt32;
192 item->u.int32Value = value;
193 return overwrote;
194}
195
196bool MediaAnalyticsItem::setInt64(MediaAnalyticsItem::Attr attr, int64_t value) {
197 ssize_t i = mItems.indexOfKey(attr);
198 bool overwrote = true;
199 if (i<0) {
200 sp<Item> item = new Item();
201 i = mItems.add(attr, item);
202 overwrote = false;
203 }
204 sp<Item> &item = mItems.editValueAt(i);
205 item->mType = MediaAnalyticsItem::Item::kTypeInt64;
206 item->u.int64Value = value;
207 return overwrote;
208}
209
210bool MediaAnalyticsItem::setDouble(MediaAnalyticsItem::Attr attr, double value) {
211 ssize_t i = mItems.indexOfKey(attr);
212 bool overwrote = true;
213 if (i<0) {
214 sp<Item> item = new Item();
215 i = mItems.add(attr, item);
216 overwrote = false;
217 }
218 sp<Item> &item = mItems.editValueAt(i);
219 item->mType = MediaAnalyticsItem::Item::kTypeDouble;
220 item->u.doubleValue = value;
221 return overwrote;
222}
223
224bool MediaAnalyticsItem::setCString(MediaAnalyticsItem::Attr attr, const char *value) {
225 bool overwrote = true;
226 if (value == NULL) return false;
227 // we store our own copy of the supplied string
228 char *nvalue = strdup(value);
229 if (nvalue == NULL) {
230 return false;
231 }
232 ssize_t i = mItems.indexOfKey(attr);
233 if (i<0) {
234 sp<Item> item = new Item();
235 i = mItems.add(attr, item);
236 overwrote = false;
237 }
238 sp<Item> &item = mItems.editValueAt(i);
239 if (item->mType == MediaAnalyticsItem::Item::kTypeCString
240 && item->u.CStringValue != NULL) {
241 free(item->u.CStringValue);
242 item->u.CStringValue = NULL;
243 }
244 item->mType = MediaAnalyticsItem::Item::kTypeCString;
245 item->u.CStringValue = nvalue;
246 return true;
247}
248
249// find/add/set fused into a single operation
250bool MediaAnalyticsItem::addInt32(MediaAnalyticsItem::Attr attr, int32_t value) {
251 ssize_t i = mItems.indexOfKey(attr);
252 bool overwrote = true;
253 if (i<0) {
254 sp<Item> item = new Item();
255 i = mItems.add(attr, item);
256 overwrote = false;
257 }
258 sp<Item> &item = mItems.editValueAt(i);
259 if (overwrote
260 && item->mType == MediaAnalyticsItem::Item::kTypeInt32) {
261 item->u.int32Value += value;
262 } else {
263 // start clean if there was a type mismatch
264 item->u.int32Value = value;
265 }
266 item->mType = MediaAnalyticsItem::Item::kTypeInt32;
267 return overwrote;
268}
269
270bool MediaAnalyticsItem::addInt64(MediaAnalyticsItem::Attr attr, int64_t value) {
271 ssize_t i = mItems.indexOfKey(attr);
272 bool overwrote = true;
273 if (i<0) {
274 sp<Item> item = new Item();
275 i = mItems.add(attr, item);
276 overwrote = false;
277 }
278 sp<Item> &item = mItems.editValueAt(i);
279 if (overwrote
280 && item->mType == MediaAnalyticsItem::Item::kTypeInt64) {
281 item->u.int64Value += value;
282 } else {
283 // start clean if there was a type mismatch
284 item->u.int64Value = value;
285 }
286 item->mType = MediaAnalyticsItem::Item::kTypeInt64;
287 return overwrote;
288}
289
290bool MediaAnalyticsItem::addDouble(MediaAnalyticsItem::Attr attr, double value) {
291 ssize_t i = mItems.indexOfKey(attr);
292 bool overwrote = true;
293 if (i<0) {
294 sp<Item> item = new Item();
295 i = mItems.add(attr, item);
296 overwrote = false;
297 }
298 sp<Item> &item = mItems.editValueAt(i);
299 if (overwrote
300 && item->mType == MediaAnalyticsItem::Item::kTypeDouble) {
301 item->u.doubleValue += value;
302 } else {
303 // start clean if there was a type mismatch
304 item->u.doubleValue = value;
305 }
306 item->mType = MediaAnalyticsItem::Item::kTypeDouble;
307 return overwrote;
308}
309
310// find & extract values
311bool MediaAnalyticsItem::getInt32(MediaAnalyticsItem::Attr attr, int32_t *value) {
312 ssize_t i = mItems.indexOfKey(attr);
313 if (i < 0) {
314 return false;
315 }
316 sp<Item> &item = mItems.editValueAt(i);
317 *value = item->u.int32Value;
318 return true;
319}
320bool MediaAnalyticsItem::getInt64(MediaAnalyticsItem::Attr attr, int64_t *value) {
321 ssize_t i = mItems.indexOfKey(attr);
322 if (i < 0) {
323 return false;
324 }
325 sp<Item> &item = mItems.editValueAt(i);
326 *value = item->u.int64Value;
327 return true;
328}
329bool MediaAnalyticsItem::getDouble(MediaAnalyticsItem::Attr attr, double *value) {
330 ssize_t i = mItems.indexOfKey(attr);
331 if (i < 0) {
332 return false;
333 }
334 sp<Item> &item = mItems.editValueAt(i);
335 *value = item->u.doubleValue;
336 return true;
337}
338
339// caller responsible for the returned string
340bool MediaAnalyticsItem::getCString(MediaAnalyticsItem::Attr attr, char **value) {
341 ssize_t i = mItems.indexOfKey(attr);
342 if (i < 0) {
343 return false;
344 }
345 sp<Item> &item = mItems.editValueAt(i);
346 char *p = strdup(item->u.CStringValue);
347 *value = p;
348 return true;
349}
350
351// remove indicated keys and their values
352// return value is # keys removed
353int32_t MediaAnalyticsItem::filter(int n, MediaAnalyticsItem::Attr attrs[]) {
354 int zapped = 0;
355 if (attrs == NULL) {
356 return -1;
357 }
358 if (n <= 0) {
359 return -1;
360 }
361 for (ssize_t i = 0 ; i < n ; i++) {
362 ssize_t j = mItems.indexOfKey(attrs[i]);
363 if (j >= 0) {
364 mItems.removeItemsAt(j);
365 zapped++;
366 }
367 }
368 return zapped;
369}
370
371// remove any keys NOT in the provided list
372// return value is # keys removed
373int32_t MediaAnalyticsItem::filterNot(int n, MediaAnalyticsItem::Attr attrs[]) {
374 int zapped = 0;
375 if (attrs == NULL) {
376 return -1;
377 }
378 if (n <= 0) {
379 return -1;
380 }
381 for (ssize_t i = mItems.size()-1 ; i >=0 ; i--) {
382 const MediaAnalyticsItem::Attr& lattr = mItems.keyAt(i);
383 ssize_t j;
384 for (j= 0; j < n ; j++) {
385 if (lattr == attrs[j]) {
386 mItems.removeItemsAt(i);
387 zapped++;
388 break;
389 }
390 }
391 }
392 return zapped;
393}
394
395// remove a single key
396// return value is 0 (not found) or 1 (found and removed)
397int32_t MediaAnalyticsItem::filter(MediaAnalyticsItem::Attr attr) {
398 if (attr == 0) return -1;
399 ssize_t i = mItems.indexOfKey(attr);
400 if (i < 0) {
401 return 0;
402 }
403 mItems.removeItemsAt(i);
404 return 1;
405}
406
407
408// handle individual items/properties stored within the class
409//
410MediaAnalyticsItem::Item::Item()
411 : mType(kTypeNone)
412{
413}
414
415MediaAnalyticsItem::Item::~Item()
416{
417 clear();
418}
419
420void MediaAnalyticsItem::Item::clear()
421{
422 if (mType == kTypeCString && u.CStringValue != NULL) {
423 free(u.CStringValue);
424 u.CStringValue = NULL;
425 }
426 mType = kTypeNone;
427}
428
429// Parcel / serialize things for binder calls
430//
431
432int32_t MediaAnalyticsItem::readFromParcel(const Parcel& data) {
433 // into 'this' object
434 // .. we make a copy of the string to put away.
435 mKey = data.readCString();
436 mSessionID = data.readInt64();
437 mFinalized = data.readInt32();
438 mTimestamp = data.readInt64();
439
440 int count = data.readInt32();
441 for (int i = 0; i < count ; i++) {
442 MediaAnalyticsItem::Attr attr = data.readCString();
443 int32_t ztype = data.readInt32();
444 switch (ztype) {
445 case MediaAnalyticsItem::Item::kTypeInt32:
446 setInt32(attr, data.readInt32());
447 break;
448 case MediaAnalyticsItem::Item::kTypeInt64:
449 setInt64(attr, data.readInt64());
450 break;
451 case MediaAnalyticsItem::Item::kTypeDouble:
452 setDouble(attr, data.readDouble());
453 break;
454 case MediaAnalyticsItem::Item::kTypeCString:
455 setCString(attr, data.readCString());
456 break;
457 default:
458 ALOGE("reading bad item type: %d, idx %d",
459 ztype, i);
460 return -1;
461 }
462 }
463
464 return 0;
465}
466
467int32_t MediaAnalyticsItem::writeToParcel(Parcel *data) {
468 if (data == NULL) return -1;
469
470
471 data->writeCString(mKey.c_str());
472 data->writeInt64(mSessionID);
473 data->writeInt32(mFinalized);
474 data->writeInt64(mTimestamp);
475
476 // set of items
477 int count = mItems.size();
478 data->writeInt32(count);
479 for (int i = 0 ; i < count; i++ ) {
480 MediaAnalyticsItem::Attr attr = mItems.keyAt(i);
481 sp<Item> value = mItems.valueAt(i);
482 {
483 data->writeCString(attr.c_str());
484 data->writeInt32(value->mType);
485 switch (value->mType) {
486 case MediaAnalyticsItem::Item::kTypeInt32:
487 data->writeInt32(value->u.int32Value);
488 break;
489 case MediaAnalyticsItem::Item::kTypeInt64:
490 data->writeInt64(value->u.int64Value);
491 break;
492 case MediaAnalyticsItem::Item::kTypeDouble:
493 data->writeDouble(value->u.doubleValue);
494 break;
495 case MediaAnalyticsItem::Item::kTypeCString:
496 data->writeCString(value->u.CStringValue);
497 break;
498 default:
499 ALOGE("found bad item type: %d, idx %d",
500 value->mType, i);
501 break;
502 }
503 }
504 }
505
506 return 0;
507}
508
509
510
511AString MediaAnalyticsItem::toString() {
512
513 AString result = "(";
514 char buffer[256];
515
516 // same order as we spill into the parcel, although not required
517 // key+session are our primary matching criteria
518 result.append(mKey.c_str());
519 result.append(":");
520 snprintf(buffer, sizeof(buffer), "%" PRId64 ":", mSessionID);
521 result.append(buffer);
522
523 // we need these internally, but don't want to upload them
524 snprintf(buffer, sizeof(buffer), "%d:%d", mUid, mPid);
525 result.append(buffer);
526
527 snprintf(buffer, sizeof(buffer), "%d:", mFinalized);
528 result.append(buffer);
529 snprintf(buffer, sizeof(buffer), "%" PRId64 ":", mTimestamp);
530 result.append(buffer);
531
532 // set of items
533 int count = mItems.size();
534 snprintf(buffer, sizeof(buffer), "%d:", count);
535 result.append(buffer);
536 for (int i = 0 ; i < count; i++ ) {
537 const MediaAnalyticsItem::Attr attr = mItems.keyAt(i);
538 const sp<Item> value = mItems.valueAt(i);
539 switch (value->mType) {
540 case MediaAnalyticsItem::Item::kTypeInt32:
541 snprintf(buffer,sizeof(buffer),
542 "%s=%d:", attr.c_str(), value->u.int32Value);
543 break;
544 case MediaAnalyticsItem::Item::kTypeInt64:
545 snprintf(buffer,sizeof(buffer),
546 "%s=%" PRId64 ":", attr.c_str(), value->u.int64Value);
547 break;
548 case MediaAnalyticsItem::Item::kTypeDouble:
549 snprintf(buffer,sizeof(buffer),
550 "%s=%e:", attr.c_str(), value->u.doubleValue);
551 break;
552 case MediaAnalyticsItem::Item::kTypeCString:
553 // XXX: worry about escape chars
554 // XXX: worry about overflowing buffer
555 snprintf(buffer,sizeof(buffer), "%s=", attr.c_str());
556 result.append(buffer);
557 result.append(value->u.CStringValue);
558 buffer[0] = ':';
559 buffer[1] = '\0';
560 break;
561 default:
562 ALOGE("to_String bad item type: %d",
563 value->mType);
564 break;
565 }
566 result.append(buffer);
567 }
568
569 result.append(")");
570
571 return result;
572}
573
574// for the lazy, we offer methods that finds the service and
575// calls the appropriate daemon
576bool MediaAnalyticsItem::selfrecord() {
577 return selfrecord(false);
578}
579
580bool MediaAnalyticsItem::selfrecord(bool forcenew) {
581
582 AString p = this->toString();
583 ALOGD("selfrecord of: %s [forcenew=%d]", p.c_str(), forcenew);
584
585 sp<IMediaAnalyticsService> svc = getInstance();
586
587 if (svc != NULL) {
588 svc->submit(this, forcenew);
589 return true;
590 } else {
591 return false;
592 }
593}
594
595// get a connection we can reuse for most of our lifetime
596// static
597sp<IMediaAnalyticsService> MediaAnalyticsItem::sAnalyticsService;
598static Mutex sInitMutex;
599
600//static
601bool MediaAnalyticsItem::isEnabled() {
602 int enabled = property_get_int32(MediaAnalyticsItem::EnabledProperty, -1);
603
604 if (enabled == -1) {
605 enabled = property_get_int32(MediaAnalyticsItem::EnabledPropertyPersist, -1);
606 }
607 if (enabled == -1) {
608 enabled = MediaAnalyticsItem::EnabledProperty_default;
609 }
610 if (enabled <= 0) {
611 return false;
612 }
613 return true;
614}
615
616//static
617sp<IMediaAnalyticsService> MediaAnalyticsItem::getInstance() {
618 static const char *servicename = "media.analytics";
619 int enabled = isEnabled();
620
621 if (enabled == false) {
622 if (DEBUG_SERVICEACCESS) {
623 ALOGD("disabled");
624 }
625 return NULL;
626 }
627
628 {
629 Mutex::Autolock _l(sInitMutex);
630 const char *badness = "";
631
632
633 if (sAnalyticsService == NULL) {
634 sp<IServiceManager> sm = defaultServiceManager();
635 if (sm != NULL) {
636 sp<IBinder> binder = sm->getService(String16(servicename));
637 if (binder != NULL) {
638 sAnalyticsService = interface_cast<IMediaAnalyticsService>(binder);
639 } else {
640 badness = "did not find service";
641 }
642 } else {
643 badness = "No Service Manager access";
644 }
645 // always
646 if (1 || DEBUG_SERVICEACCESS) {
647 if (sAnalyticsService == NULL) {
648 ALOGD("Unable to bind to service %s: %s", servicename, badness);
649 }
650 }
651 }
652 return sAnalyticsService;
653 }
654}
655
656
657// merge the info from 'incoming' into this record.
658// we finish with a union of this+incoming and special handling for collisions
659bool MediaAnalyticsItem::merge(sp<MediaAnalyticsItem> incoming) {
660
661 // if I don't have key or session id, take them from incoming
662 // 'this' should never be missing both of them...
663 if (mKey.empty()) {
664 mKey = incoming->mKey;
665 } else if (mSessionID == 0) {
666 mSessionID = incoming->mSessionID;
667 }
668
669 // we always take the more recent 'finalized' value
670 setFinalized(incoming->getFinalized());
671
672 // for each attribute from 'incoming', resolve appropriately
673 int nattr = incoming->mItems.size();
674 for (int i = 0 ; i < nattr; i++ ) {
675 const MediaAnalyticsItem::Attr attr = incoming->mItems.keyAt(i);
676 const sp<Item> value = incoming->mItems.valueAt(i);
677
678 const char *p = attr.c_str();
679 char semantic = p[strlen(p)-1];
680
681 switch (semantic) {
682 default: // default operation is keep new
683 case '>': // last aka keep new
684 mItems.replaceValueFor(attr, value);
685 break;
686
687 case '<': /* first aka keep first*/
688 /* nop */
689 break;
690
691 case '+': /* sum */
692 // XXX validate numeric types, sum in place
693 break;
694
695 }
696 }
697
698 // not sure when we'd return false...
699 return true;
700}
701
702} // namespace android
703