1/*
2 * Copyright (C) 2009 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//#define LOG_NDEBUG 0
18#define LOG_TAG "TestPlayerStub"
19#include "utils/Log.h"
20
21#include "TestPlayerStub.h"
22
23#include <dlfcn.h>  // for dlopen/dlclose
24#include <stdlib.h>
25#include <string.h>
26#include <cutils/properties.h>
27#include <utils/Errors.h>  // for status_t
28
29#include "media/MediaPlayerInterface.h"
30
31
32namespace {
33using android::status_t;
34using android::MediaPlayerBase;
35
36const char *kTestUrlScheme = "test:";
37const char *kUrlParam = "url=";
38
39const char *kBuildTypePropName = "ro.build.type";
40const char *kEngBuild = "eng";
41const char *kTestBuild = "test";
42
43// @return true if the current build is 'eng' or 'test'.
44bool isTestBuild()
45{
46    char prop[PROPERTY_VALUE_MAX] = { '\0', };
47
48    property_get(kBuildTypePropName, prop, '\0');
49    return strcmp(prop, kEngBuild) == 0 || strcmp(prop, kTestBuild) == 0;
50}
51
52// @return true if the url scheme is 'test:'
53bool isTestUrl(const char *url)
54{
55    return url && strncmp(url, kTestUrlScheme, strlen(kTestUrlScheme)) == 0;
56}
57
58}  // anonymous namespace
59
60namespace android {
61
62TestPlayerStub::TestPlayerStub()
63    :mUrl(NULL), mFilename(NULL), mContentUrl(NULL),
64     mHandle(NULL), mNewPlayer(NULL), mDeletePlayer(NULL),
65     mPlayer(NULL) { }
66
67TestPlayerStub::~TestPlayerStub()
68{
69    resetInternal();
70}
71
72status_t TestPlayerStub::initCheck()
73{
74    return isTestBuild() ? OK : INVALID_OPERATION;
75}
76
77// Parse mUrl to get:
78// * The library to be dlopened.
79// * The url to be passed to the real setDataSource impl.
80//
81// mUrl is expected to be in following format:
82//
83// test:<name of the .so>?url=<url for setDataSource>
84//
85// The value of the url parameter is treated as a string (no
86// unescaping of illegal charaters).
87status_t TestPlayerStub::parseUrl()
88{
89    if (strlen(mUrl) < strlen(kTestUrlScheme)) {
90        resetInternal();
91        return BAD_VALUE;
92    }
93
94    char *i = mUrl + strlen(kTestUrlScheme);
95
96    mFilename = i;
97
98    while (*i != '\0' && *i != '?') {
99        ++i;
100    }
101
102    if (*i == '\0' || strncmp(i + 1, kUrlParam, strlen(kUrlParam)) != 0) {
103        resetInternal();
104        return BAD_VALUE;
105    }
106    *i = '\0';  // replace '?' to nul-terminate mFilename
107
108    mContentUrl = i + 1 + strlen(kUrlParam);
109    return OK;
110}
111
112// Load the dynamic library.
113// Create the test player.
114// Call setDataSource on the test player with the url in param.
115status_t TestPlayerStub::setDataSource(
116        const sp<IMediaHTTPService> &httpService,
117        const char *url,
118        const KeyedVector<String8, String8> *headers) {
119    if (!isTestUrl(url) || NULL != mHandle) {
120        return INVALID_OPERATION;
121    }
122
123    mUrl = strdup(url);
124
125    status_t status = parseUrl();
126
127    if (OK != status) {
128        resetInternal();
129        return status;
130    }
131
132    ::dlerror();  // Clears any pending error.
133
134    // Load the test player from the url. dlopen will fail if the lib
135    // is not there. dls are under /system/lib
136    // None of the entry points should be NULL.
137    mHandle = ::dlopen(mFilename, RTLD_NOW | RTLD_GLOBAL);
138    if (!mHandle) {
139        ALOGE("dlopen failed: %s", ::dlerror());
140        resetInternal();
141        return UNKNOWN_ERROR;
142    }
143
144    // Load the 2 entry points to create and delete instances.
145    const char *err;
146    mNewPlayer = reinterpret_cast<NEW_PLAYER>(dlsym(mHandle,
147                                                    "newPlayer"));
148    err = ::dlerror();
149    if (err || mNewPlayer == NULL) {
150        // if err is NULL the string <null> is inserted in the logs =>
151        // mNewPlayer was NULL.
152        ALOGE("dlsym for newPlayer failed %s", err);
153        resetInternal();
154        return UNKNOWN_ERROR;
155    }
156
157    mDeletePlayer = reinterpret_cast<DELETE_PLAYER>(dlsym(mHandle,
158                                                          "deletePlayer"));
159    err = ::dlerror();
160    if (err || mDeletePlayer == NULL) {
161        ALOGE("dlsym for deletePlayer failed %s", err);
162        resetInternal();
163        return UNKNOWN_ERROR;
164    }
165
166    mPlayer = (*mNewPlayer)();
167    return mPlayer->setDataSource(httpService, mContentUrl, headers);
168}
169
170// Internal cleanup.
171status_t TestPlayerStub::resetInternal()
172{
173    if(mUrl) {
174        free(mUrl);
175        mUrl = NULL;
176    }
177    mFilename = NULL;
178    mContentUrl = NULL;
179
180    if (mPlayer) {
181        ALOG_ASSERT(mDeletePlayer != NULL, "mDeletePlayer is null");
182        (*mDeletePlayer)(mPlayer);
183        mPlayer = NULL;
184    }
185
186    if (mHandle) {
187        ::dlclose(mHandle);
188        mHandle = NULL;
189    }
190    return OK;
191}
192
193/* static */ bool TestPlayerStub::canBeUsed(const char *url)
194{
195    return isTestBuild() && isTestUrl(url);
196}
197
198}  // namespace android
199