blob: 7d0d83d5672b21f45f546f216ff751c8c188991d [file] [log] [blame]
Kevin Rocard4bcd67f2018-02-28 14:33:38 -08001/*
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#include <stdio.h>
18
19#define LOG_TAG "DeviceHalHidl"
20//#define LOG_NDEBUG 0
21
Kevin Rocard95213bf2018-11-08 17:16:57 -080022#include PATH(android/hardware/audio/FILE_VERSION/IPrimaryDevice.h)
Kevin Rocard4bcd67f2018-02-28 14:33:38 -080023#include <cutils/native_handle.h>
24#include <hwbinder/IPCThreadState.h>
jiabindaf49952019-11-22 14:10:57 -080025#include <media/AudioContainers.h>
Kevin Rocard4bcd67f2018-02-28 14:33:38 -080026#include <utils/Log.h>
27
Kevin Rocardb9cfbf12018-02-23 19:11:06 -080028#include <common/all-versions/VersionUtils.h>
29
Kevin Rocard4bcd67f2018-02-28 14:33:38 -080030#include "DeviceHalHidl.h"
Eric Laurentb82e6b72019-11-22 17:25:04 -080031#include "EffectHalHidl.h"
Kevin Rocard4bcd67f2018-02-28 14:33:38 -080032#include "HidlUtils.h"
33#include "StreamHalHidl.h"
Kevin Rocardb9cfbf12018-02-23 19:11:06 -080034#include "VersionUtils.h"
Kevin Rocard4bcd67f2018-02-28 14:33:38 -080035
Mikhail Naganov9ccaa162018-12-12 10:27:29 -080036using ::android::hardware::audio::common::CPP_VERSION::implementation::HidlUtils;
Kevin Rocard7a9f05a2018-11-28 16:52:25 -080037using ::android::hardware::audio::common::utils::EnumBitfield;
Kevin Rocard4bcd67f2018-02-28 14:33:38 -080038using ::android::hardware::hidl_string;
39using ::android::hardware::hidl_vec;
40
41namespace android {
Kevin Rocard070e7512018-05-22 09:29:13 -070042namespace CPP_VERSION {
Kevin Rocard4bcd67f2018-02-28 14:33:38 -080043
Mikhail Naganov9ccaa162018-12-12 10:27:29 -080044using namespace ::android::hardware::audio::common::CPP_VERSION;
45using namespace ::android::hardware::audio::CPP_VERSION;
46
Eric Laurentb82e6b72019-11-22 17:25:04 -080047using EffectHalHidl = ::android::effect::CPP_VERSION::EffectHalHidl;
48
Kevin Rocard4bcd67f2018-02-28 14:33:38 -080049namespace {
50
51status_t deviceAddressFromHal(
52 audio_devices_t device, const char* halAddress, DeviceAddress* address) {
53 address->device = AudioDevice(device);
54
Kevin Rocard5915fa32018-03-29 10:32:44 -070055 if (halAddress == nullptr || strnlen(halAddress, AUDIO_DEVICE_MAX_ADDRESS_LEN) == 0) {
Kevin Rocard4bcd67f2018-02-28 14:33:38 -080056 return OK;
57 }
jiabindaf49952019-11-22 14:10:57 -080058 if (getAudioDeviceOutAllA2dpSet().count(device) > 0
59 || device == AUDIO_DEVICE_IN_BLUETOOTH_A2DP) {
Kevin Rocard4bcd67f2018-02-28 14:33:38 -080060 int status = sscanf(halAddress,
61 "%hhX:%hhX:%hhX:%hhX:%hhX:%hhX",
62 &address->address.mac[0], &address->address.mac[1], &address->address.mac[2],
63 &address->address.mac[3], &address->address.mac[4], &address->address.mac[5]);
64 return status == 6 ? OK : BAD_VALUE;
jiabindaf49952019-11-22 14:10:57 -080065 } else if (device == AUDIO_DEVICE_OUT_IP || device == AUDIO_DEVICE_IN_IP) {
Kevin Rocard4bcd67f2018-02-28 14:33:38 -080066 int status = sscanf(halAddress,
67 "%hhu.%hhu.%hhu.%hhu",
68 &address->address.ipv4[0], &address->address.ipv4[1],
69 &address->address.ipv4[2], &address->address.ipv4[3]);
70 return status == 4 ? OK : BAD_VALUE;
jiabindaf49952019-11-22 14:10:57 -080071 } else if (getAudioDeviceOutAllUsbSet().count(device) > 0
72 || getAudioDeviceInAllUsbSet().count(device) > 0) {
Kevin Rocard4bcd67f2018-02-28 14:33:38 -080073 int status = sscanf(halAddress,
74 "card=%d;device=%d",
75 &address->address.alsa.card, &address->address.alsa.device);
76 return status == 2 ? OK : BAD_VALUE;
jiabindaf49952019-11-22 14:10:57 -080077 } else if (device == AUDIO_DEVICE_OUT_BUS || device == AUDIO_DEVICE_IN_BUS) {
78 address->busAddress = halAddress;
79 return OK;
80 } else if (device == AUDIO_DEVICE_OUT_REMOTE_SUBMIX
81 || device == AUDIO_DEVICE_IN_REMOTE_SUBMIX) {
82 address->rSubmixAddress = halAddress;
83 return OK;
Kevin Rocard4bcd67f2018-02-28 14:33:38 -080084 }
85 return OK;
86}
87
88} // namespace
89
90DeviceHalHidl::DeviceHalHidl(const sp<IDevice>& device)
91 : ConversionHelperHidl("Device"), mDevice(device),
92 mPrimaryDevice(IPrimaryDevice::castFrom(device)) {
93}
94
95DeviceHalHidl::~DeviceHalHidl() {
96 if (mDevice != 0) {
Mikhail Naganov3355e442019-11-20 14:20:01 -080097#if MAJOR_VERSION <= 5
Kevin Rocard4bcd67f2018-02-28 14:33:38 -080098 mDevice.clear();
99 hardware::IPCThreadState::self()->flushCommands();
Mikhail Naganov3355e442019-11-20 14:20:01 -0800100#elif MAJOR_VERSION >= 6
101 mDevice->close();
102#endif
Kevin Rocard4bcd67f2018-02-28 14:33:38 -0800103 }
104}
105
106status_t DeviceHalHidl::getSupportedDevices(uint32_t*) {
107 // Obsolete.
108 return INVALID_OPERATION;
109}
110
111status_t DeviceHalHidl::initCheck() {
112 if (mDevice == 0) return NO_INIT;
113 return processReturn("initCheck", mDevice->initCheck());
114}
115
116status_t DeviceHalHidl::setVoiceVolume(float volume) {
117 if (mDevice == 0) return NO_INIT;
118 if (mPrimaryDevice == 0) return INVALID_OPERATION;
119 return processReturn("setVoiceVolume", mPrimaryDevice->setVoiceVolume(volume));
120}
121
122status_t DeviceHalHidl::setMasterVolume(float volume) {
123 if (mDevice == 0) return NO_INIT;
Mikhail Naganovae1f6622019-02-21 15:20:05 -0800124 return processReturn("setMasterVolume", mDevice->setMasterVolume(volume));
Kevin Rocard4bcd67f2018-02-28 14:33:38 -0800125}
126
127status_t DeviceHalHidl::getMasterVolume(float *volume) {
128 if (mDevice == 0) return NO_INIT;
Kevin Rocard4bcd67f2018-02-28 14:33:38 -0800129 Result retval;
Mikhail Naganovae1f6622019-02-21 15:20:05 -0800130 Return<void> ret = mDevice->getMasterVolume(
Kevin Rocard4bcd67f2018-02-28 14:33:38 -0800131 [&](Result r, float v) {
132 retval = r;
133 if (retval == Result::OK) {
134 *volume = v;
135 }
136 });
137 return processReturn("getMasterVolume", ret, retval);
138}
139
140status_t DeviceHalHidl::setMode(audio_mode_t mode) {
141 if (mDevice == 0) return NO_INIT;
142 if (mPrimaryDevice == 0) return INVALID_OPERATION;
143 return processReturn("setMode", mPrimaryDevice->setMode(AudioMode(mode)));
144}
145
146status_t DeviceHalHidl::setMicMute(bool state) {
147 if (mDevice == 0) return NO_INIT;
148 return processReturn("setMicMute", mDevice->setMicMute(state));
149}
150
151status_t DeviceHalHidl::getMicMute(bool *state) {
152 if (mDevice == 0) return NO_INIT;
153 Result retval;
154 Return<void> ret = mDevice->getMicMute(
155 [&](Result r, bool mute) {
156 retval = r;
157 if (retval == Result::OK) {
158 *state = mute;
159 }
160 });
161 return processReturn("getMicMute", ret, retval);
162}
163
164status_t DeviceHalHidl::setMasterMute(bool state) {
165 if (mDevice == 0) return NO_INIT;
166 return processReturn("setMasterMute", mDevice->setMasterMute(state));
167}
168
169status_t DeviceHalHidl::getMasterMute(bool *state) {
170 if (mDevice == 0) return NO_INIT;
171 Result retval;
172 Return<void> ret = mDevice->getMasterMute(
173 [&](Result r, bool mute) {
174 retval = r;
175 if (retval == Result::OK) {
176 *state = mute;
177 }
178 });
179 return processReturn("getMasterMute", ret, retval);
180}
181
182status_t DeviceHalHidl::setParameters(const String8& kvPairs) {
183 if (mDevice == 0) return NO_INIT;
184 hidl_vec<ParameterValue> hidlParams;
185 status_t status = parametersFromHal(kvPairs, &hidlParams);
186 if (status != OK) return status;
Kevin Rocardb9cfbf12018-02-23 19:11:06 -0800187 // TODO: change the API so that context and kvPairs are separated
188 return processReturn("setParameters",
189 utils::setParameters(mDevice, {} /* context */, hidlParams));
Kevin Rocard4bcd67f2018-02-28 14:33:38 -0800190}
191
192status_t DeviceHalHidl::getParameters(const String8& keys, String8 *values) {
193 values->clear();
194 if (mDevice == 0) return NO_INIT;
195 hidl_vec<hidl_string> hidlKeys;
196 status_t status = keysFromHal(keys, &hidlKeys);
197 if (status != OK) return status;
198 Result retval;
Kevin Rocardb9cfbf12018-02-23 19:11:06 -0800199 Return<void> ret = utils::getParameters(mDevice,
200 {} /* context */,
Kevin Rocard4bcd67f2018-02-28 14:33:38 -0800201 hidlKeys,
202 [&](Result r, const hidl_vec<ParameterValue>& parameters) {
203 retval = r;
204 if (retval == Result::OK) {
205 parametersToHal(parameters, values);
206 }
207 });
208 return processReturn("getParameters", ret, retval);
209}
210
211status_t DeviceHalHidl::getInputBufferSize(
212 const struct audio_config *config, size_t *size) {
213 if (mDevice == 0) return NO_INIT;
214 AudioConfig hidlConfig;
215 HidlUtils::audioConfigFromHal(*config, &hidlConfig);
216 Result retval;
217 Return<void> ret = mDevice->getInputBufferSize(
218 hidlConfig,
219 [&](Result r, uint64_t bufferSize) {
220 retval = r;
221 if (retval == Result::OK) {
222 *size = static_cast<size_t>(bufferSize);
223 }
224 });
225 return processReturn("getInputBufferSize", ret, retval);
226}
227
228status_t DeviceHalHidl::openOutputStream(
229 audio_io_handle_t handle,
jiabin43810402019-10-24 14:58:31 -0700230 audio_devices_t deviceType,
Kevin Rocard4bcd67f2018-02-28 14:33:38 -0800231 audio_output_flags_t flags,
232 struct audio_config *config,
233 const char *address,
234 sp<StreamOutHalInterface> *outStream) {
235 if (mDevice == 0) return NO_INIT;
236 DeviceAddress hidlDevice;
jiabin43810402019-10-24 14:58:31 -0700237 status_t status = deviceAddressFromHal(deviceType, address, &hidlDevice);
Kevin Rocard4bcd67f2018-02-28 14:33:38 -0800238 if (status != OK) return status;
239 AudioConfig hidlConfig;
240 HidlUtils::audioConfigFromHal(*config, &hidlConfig);
241 Result retval = Result::NOT_INITIALIZED;
242 Return<void> ret = mDevice->openOutputStream(
243 handle,
244 hidlDevice,
245 hidlConfig,
Kevin Rocard7a9f05a2018-11-28 16:52:25 -0800246 EnumBitfield<AudioOutputFlag>(flags),
Kevin Rocard3d48dce2018-11-08 17:16:57 -0800247#if MAJOR_VERSION >= 4
Kevin Rocardb9cfbf12018-02-23 19:11:06 -0800248 {} /* metadata */,
Kevin Rocard070e7512018-05-22 09:29:13 -0700249#endif
Kevin Rocard4bcd67f2018-02-28 14:33:38 -0800250 [&](Result r, const sp<IStreamOut>& result, const AudioConfig& suggestedConfig) {
251 retval = r;
252 if (retval == Result::OK) {
253 *outStream = new StreamOutHalHidl(result);
254 }
255 HidlUtils::audioConfigToHal(suggestedConfig, config);
256 });
257 return processReturn("openOutputStream", ret, retval);
258}
259
260status_t DeviceHalHidl::openInputStream(
261 audio_io_handle_t handle,
262 audio_devices_t devices,
263 struct audio_config *config,
264 audio_input_flags_t flags,
265 const char *address,
266 audio_source_t source,
Mikhail Naganovb4e037e2019-01-14 15:56:33 -0800267 audio_devices_t outputDevice,
268 const char *outputDeviceAddress,
Kevin Rocard4bcd67f2018-02-28 14:33:38 -0800269 sp<StreamInHalInterface> *inStream) {
270 if (mDevice == 0) return NO_INIT;
271 DeviceAddress hidlDevice;
272 status_t status = deviceAddressFromHal(devices, address, &hidlDevice);
273 if (status != OK) return status;
274 AudioConfig hidlConfig;
275 HidlUtils::audioConfigFromHal(*config, &hidlConfig);
276 Result retval = Result::NOT_INITIALIZED;
Kevin Rocard070e7512018-05-22 09:29:13 -0700277#if MAJOR_VERSION == 2
Mikhail Naganovd9499eb2018-12-17 16:23:22 -0800278 auto sinkMetadata = AudioSource(source);
Kevin Rocard3d48dce2018-11-08 17:16:57 -0800279#elif MAJOR_VERSION >= 4
Kevin Rocardb9cfbf12018-02-23 19:11:06 -0800280 // TODO: correctly propagate the tracks sources and volume
281 // for now, only send the main source at 1dbfs
Mikhail Naganovd9499eb2018-12-17 16:23:22 -0800282 SinkMetadata sinkMetadata = {{{ .source = AudioSource(source), .gain = 1 }}};
Kevin Rocard070e7512018-05-22 09:29:13 -0700283#endif
Mikhail Naganovb4e037e2019-01-14 15:56:33 -0800284#if MAJOR_VERSION < 5
285 (void)outputDevice;
286 (void)outputDeviceAddress;
287#else
288 if (outputDevice != AUDIO_DEVICE_NONE) {
289 DeviceAddress hidlOutputDevice;
290 status = deviceAddressFromHal(outputDevice, outputDeviceAddress, &hidlOutputDevice);
291 if (status != OK) return status;
292 sinkMetadata.tracks[0].destination.device(std::move(hidlOutputDevice));
293 }
294#endif
Mikhail Naganovbad15102019-12-10 16:44:19 -0800295#if MAJOR_VERSION <= 5
296 // Some flags were specific to framework and must not leak to the HAL.
297 flags = static_cast<audio_input_flags_t>(flags & ~AUDIO_INPUT_FLAG_DIRECT);
298#endif
Kevin Rocard4bcd67f2018-02-28 14:33:38 -0800299 Return<void> ret = mDevice->openInputStream(
300 handle,
301 hidlDevice,
302 hidlConfig,
Kevin Rocard7a9f05a2018-11-28 16:52:25 -0800303 EnumBitfield<AudioInputFlag>(flags),
Mikhail Naganovd9499eb2018-12-17 16:23:22 -0800304 sinkMetadata,
Kevin Rocard4bcd67f2018-02-28 14:33:38 -0800305 [&](Result r, const sp<IStreamIn>& result, const AudioConfig& suggestedConfig) {
306 retval = r;
307 if (retval == Result::OK) {
308 *inStream = new StreamInHalHidl(result);
309 }
310 HidlUtils::audioConfigToHal(suggestedConfig, config);
311 });
312 return processReturn("openInputStream", ret, retval);
313}
314
315status_t DeviceHalHidl::supportsAudioPatches(bool *supportsPatches) {
316 if (mDevice == 0) return NO_INIT;
317 return processReturn("supportsAudioPatches", mDevice->supportsAudioPatches(), supportsPatches);
318}
319
320status_t DeviceHalHidl::createAudioPatch(
321 unsigned int num_sources,
322 const struct audio_port_config *sources,
323 unsigned int num_sinks,
324 const struct audio_port_config *sinks,
325 audio_patch_handle_t *patch) {
326 if (mDevice == 0) return NO_INIT;
Eric Laurent8711cfd2019-06-10 18:06:33 -0700327 if (patch == nullptr) return BAD_VALUE;
328
Mikhail Naganov73bdf572019-12-11 12:34:15 -0800329#if MAJOR_VERSION < 6
Eric Laurent8711cfd2019-06-10 18:06:33 -0700330 if (*patch != AUDIO_PATCH_HANDLE_NONE) {
331 status_t status = releaseAudioPatch(*patch);
332 ALOGW_IF(status != NO_ERROR, "%s error %d releasing patch handle %d",
333 __func__, status, *patch);
Mikhail Naganov73bdf572019-12-11 12:34:15 -0800334 *patch = AUDIO_PATCH_HANDLE_NONE;
Eric Laurent8711cfd2019-06-10 18:06:33 -0700335 }
Mikhail Naganov73bdf572019-12-11 12:34:15 -0800336#endif
Eric Laurent8711cfd2019-06-10 18:06:33 -0700337
Kevin Rocard4bcd67f2018-02-28 14:33:38 -0800338 hidl_vec<AudioPortConfig> hidlSources, hidlSinks;
339 HidlUtils::audioPortConfigsFromHal(num_sources, sources, &hidlSources);
340 HidlUtils::audioPortConfigsFromHal(num_sinks, sinks, &hidlSinks);
Mikhail Naganov73bdf572019-12-11 12:34:15 -0800341 Result retval = Result::OK;
342 Return<void> ret;
343 std::string methodName = "createAudioPatch";
344 if (*patch == AUDIO_PATCH_HANDLE_NONE) { // always true for MAJOR_VERSION < 6
345 ret = mDevice->createAudioPatch(
346 hidlSources, hidlSinks,
347 [&](Result r, AudioPatchHandle hidlPatch) {
348 retval = r;
349 if (retval == Result::OK) {
350 *patch = static_cast<audio_patch_handle_t>(hidlPatch);
351 }
352 });
353 } else {
354#if MAJOR_VERSION >= 6
355 ret = mDevice->updateAudioPatch(
356 *patch,
357 hidlSources, hidlSinks,
358 [&](Result r, AudioPatchHandle hidlPatch) {
359 retval = r;
360 if (retval == Result::OK) {
361 *patch = static_cast<audio_patch_handle_t>(hidlPatch);
362 }
363 });
364 methodName = "updateAudioPatch";
365#endif
366 }
367 return processReturn(methodName.c_str(), ret, retval);
Kevin Rocard4bcd67f2018-02-28 14:33:38 -0800368}
369
370status_t DeviceHalHidl::releaseAudioPatch(audio_patch_handle_t patch) {
371 if (mDevice == 0) return NO_INIT;
372 return processReturn("releaseAudioPatch", mDevice->releaseAudioPatch(patch));
373}
374
375status_t DeviceHalHidl::getAudioPort(struct audio_port *port) {
376 if (mDevice == 0) return NO_INIT;
377 AudioPort hidlPort;
378 HidlUtils::audioPortFromHal(*port, &hidlPort);
379 Result retval;
380 Return<void> ret = mDevice->getAudioPort(
381 hidlPort,
382 [&](Result r, const AudioPort& p) {
383 retval = r;
384 if (retval == Result::OK) {
385 HidlUtils::audioPortToHal(p, port);
386 }
387 });
388 return processReturn("getAudioPort", ret, retval);
389}
390
391status_t DeviceHalHidl::setAudioPortConfig(const struct audio_port_config *config) {
392 if (mDevice == 0) return NO_INIT;
393 AudioPortConfig hidlConfig;
394 HidlUtils::audioPortConfigFromHal(*config, &hidlConfig);
395 return processReturn("setAudioPortConfig", mDevice->setAudioPortConfig(hidlConfig));
396}
397
Kevin Rocard070e7512018-05-22 09:29:13 -0700398#if MAJOR_VERSION == 2
399status_t DeviceHalHidl::getMicrophones(
400 std::vector<media::MicrophoneInfo> *microphonesInfo __unused) {
401 if (mDevice == 0) return NO_INIT;
402 return INVALID_OPERATION;
403}
Kevin Rocard3d48dce2018-11-08 17:16:57 -0800404#elif MAJOR_VERSION >= 4
jiabin9ff780e2018-03-19 18:19:52 -0700405status_t DeviceHalHidl::getMicrophones(std::vector<media::MicrophoneInfo> *microphonesInfo) {
406 if (mDevice == 0) return NO_INIT;
407 Result retval;
408 Return<void> ret = mDevice->getMicrophones(
409 [&](Result r, hidl_vec<MicrophoneInfo> micArrayHal) {
410 retval = r;
411 for (size_t k = 0; k < micArrayHal.size(); k++) {
412 audio_microphone_characteristic_t dst;
413 //convert
414 microphoneInfoToHal(micArrayHal[k], &dst);
415 media::MicrophoneInfo microphone = media::MicrophoneInfo(dst);
416 microphonesInfo->push_back(microphone);
417 }
418 });
419 return processReturn("getMicrophones", ret, retval);
420}
Kevin Rocard070e7512018-05-22 09:29:13 -0700421#endif
jiabin9ff780e2018-03-19 18:19:52 -0700422
Eric Laurentb82e6b72019-11-22 17:25:04 -0800423#if MAJOR_VERSION >= 6
424status_t DeviceHalHidl::addDeviceEffect(
425 audio_port_handle_t device, sp<EffectHalInterface> effect) {
426 if (mDevice == 0) return NO_INIT;
427 return processReturn("addDeviceEffect", mDevice->addDeviceEffect(
428 static_cast<AudioPortHandle>(device),
429 static_cast<EffectHalHidl*>(effect.get())->effectId()));
430}
431#else
432status_t DeviceHalHidl::addDeviceEffect(
433 audio_port_handle_t device __unused, sp<EffectHalInterface> effect __unused) {
434 return INVALID_OPERATION;
435}
436#endif
437
438#if MAJOR_VERSION >= 6
439status_t DeviceHalHidl::removeDeviceEffect(
440 audio_port_handle_t device, sp<EffectHalInterface> effect) {
441 if (mDevice == 0) return NO_INIT;
442 return processReturn("removeDeviceEffect", mDevice->removeDeviceEffect(
443 static_cast<AudioPortHandle>(device),
444 static_cast<EffectHalHidl*>(effect.get())->effectId()));
445}
446#else
447status_t DeviceHalHidl::removeDeviceEffect(
448 audio_port_handle_t device __unused, sp<EffectHalInterface> effect __unused) {
449 return INVALID_OPERATION;
450}
451#endif
452
Kevin Rocard4bcd67f2018-02-28 14:33:38 -0800453status_t DeviceHalHidl::dump(int fd) {
454 if (mDevice == 0) return NO_INIT;
455 native_handle_t* hidlHandle = native_handle_create(1, 0);
456 hidlHandle->data[0] = fd;
Kevin Rocardb9cfbf12018-02-23 19:11:06 -0800457 Return<void> ret = mDevice->debug(hidlHandle, {} /* options */);
Kevin Rocard4bcd67f2018-02-28 14:33:38 -0800458 native_handle_delete(hidlHandle);
459 return processReturn("dump", ret);
460}
461
Kevin Rocard070e7512018-05-22 09:29:13 -0700462} // namespace CPP_VERSION
Kevin Rocard4bcd67f2018-02-28 14:33:38 -0800463} // namespace android