blob: b60e7348f6259ecc1a8c155332f7ce7adcd57542 [file] [log] [blame]
Girish1f002cf2023-02-17 00:36:29 +00001/*
2**
3** Copyright 2023, The Android Open Source Project
4**
5** Licensed under the Apache License, Version 2.0 (the "License");
6** you may not use this file except in compliance with the License.
7** You may obtain a copy of the License at
8**
9** http://www.apache.org/licenses/LICENSE-2.0
10**
11** Unless required by applicable law or agreed to in writing, software
12** distributed under the License is distributed on an "AS IS" BASIS,
13** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14** See the License for the specific language governing permissions and
15** limitations under the License.
16*/
17
18//#define LOG_NDEBUG 0
19#define LOG_TAG "ResourceManagerMetrics"
20#include <utils/Log.h>
21#include <mediautils/ProcessInfo.h>
22
23#include <stats_media_metrics.h>
24
25#include "UidObserver.h"
26#include "ResourceManagerMetrics.h"
27
28#include <cmath>
29#include <sstream>
30
31namespace android {
32
33using stats::media_metrics::stats_write;
34using stats::media_metrics::MEDIA_CODEC_STARTED;
35using stats::media_metrics::MEDIA_CODEC_STOPPED;
36// Disabling this for now.
37#ifdef ENABLE_MEDIA_CODEC_CONCURRENT_USAGE_REPORTED
38using stats::media_metrics::MEDIA_CODEC_CONCURRENT_USAGE_REPORTED;
39#endif
40using stats::media_metrics::MEDIA_CODEC_RECLAIM_REQUEST_COMPLETED;
41using stats::media_metrics::MEDIA_CODEC_RECLAIM_REQUEST_COMPLETED__RECLAIM_STATUS__RECLAIM_SUCCESS;
42using stats::media_metrics::\
43 MEDIA_CODEC_RECLAIM_REQUEST_COMPLETED__RECLAIM_STATUS__RECLAIM_FAILED_NO_CLIENTS;
44using stats::media_metrics::\
45 MEDIA_CODEC_RECLAIM_REQUEST_COMPLETED__RECLAIM_STATUS__RECLAIM_FAILED_RECLAIM_RESOURCES;
46
47inline const char* getCodecType(MediaResourceSubType codecType) {
48 switch (codecType) {
49 case MediaResourceSubType::kAudioCodec: return "Audio";
50 case MediaResourceSubType::kVideoCodec: return "Video";
51 case MediaResourceSubType::kImageCodec: return "Image";
52 case MediaResourceSubType::kUnspecifiedSubType:
53 default:
54 return "Unspecified";
55 }
56 return "Unspecified";
57}
58
59static CodecBucket getCodecBucket(bool isHardware,
60 bool isEncoder,
61 MediaResourceSubType codecType) {
62 if (isHardware) {
63 switch (codecType) {
64 case MediaResourceSubType::kAudioCodec:
65 if (isEncoder) return HwAudioEncoder;
66 return HwAudioDecoder;
67 case MediaResourceSubType::kVideoCodec:
68 if (isEncoder) return HwVideoEncoder;
69 return HwVideoDecoder;
70 case MediaResourceSubType::kImageCodec:
71 if (isEncoder) return HwImageEncoder;
72 return HwImageDecoder;
73 case MediaResourceSubType::kUnspecifiedSubType:
74 default:
75 return CodecBucketUnspecified;
76 }
77 } else {
78 switch (codecType) {
79 case MediaResourceSubType::kAudioCodec:
80 if (isEncoder) return SwAudioEncoder;
81 return SwAudioDecoder;
82 case MediaResourceSubType::kVideoCodec:
83 if (isEncoder) return SwVideoEncoder;
84 return SwVideoDecoder;
85 case MediaResourceSubType::kImageCodec:
86 if (isEncoder) return SwImageEncoder;
87 return SwImageDecoder;
88 case MediaResourceSubType::kUnspecifiedSubType:
89 default:
90 return CodecBucketUnspecified;
91 }
92 }
93
94 return CodecBucketUnspecified;
95}
96
97static bool getLogMessage(int hwCount, int swCount, std::stringstream& logMsg) {
98 bool update = false;
99 logMsg.clear();
100
101 if (hwCount > 0) {
102 logMsg << " HW: " << hwCount;
103 update = true;
104 }
105 if (swCount > 0) {
106 logMsg << " SW: " << swCount;
107 update = true;
108 }
109
110 if (update) {
111 logMsg << " ] ";
112 }
113 return update;
114}
115
116ResourceManagerMetrics::ResourceManagerMetrics(const sp<ProcessInfoInterface>& processInfo) {
117 // Create a process termination watcher, with 5seconds of polling frequency.
118 mUidObserver = sp<UidObserver>::make(processInfo,
119 [this] (int32_t pid, uid_t uid) {
120 onProcessTerminated(pid, uid);
121 });
122 mUidObserver->start();
123}
124
125ResourceManagerMetrics::~ResourceManagerMetrics() {
126 mUidObserver->stop();
127}
128
129void ResourceManagerMetrics::addPid(int pid, uid_t uid) {
130 if (uid != 0) {
131 std::scoped_lock lock(mLock);
132 mUidObserver->add(pid, uid);
133 }
134}
135
136void ResourceManagerMetrics::notifyClientCreated(const ClientInfoParcel& clientInfo) {
137 std::scoped_lock lock(mLock);
138 // Update the resource instance count.
139 std::map<std::string, int>::iterator found = mConcurrentResourceCountMap.find(clientInfo.name);
140 if (found == mConcurrentResourceCountMap.end()) {
141 mConcurrentResourceCountMap[clientInfo.name] = 1;
142 } else {
143 found->second++;
144 }
145}
146
147void ResourceManagerMetrics::notifyClientReleased(const ClientInfoParcel& clientInfo) {
148 bool stopCalled = true;
149 ClientConfigMap::iterator found;
150 {
151 std::scoped_lock lock(mLock);
152 found = mClientConfigMap.find(clientInfo.id);
153 if (found != mClientConfigMap.end()) {
154 // Release is called without Stop!
155 stopCalled = false;
156 }
157 }
158 if (!stopCalled) {
159 // call Stop to update the metrics.
160 notifyClientStopped(found->second);
161 }
162 {
163 std::scoped_lock lock(mLock);
164 // Update the resource instance count also.
165 std::map<std::string, int>::iterator found =
166 mConcurrentResourceCountMap.find(clientInfo.name);
167 if (found != mConcurrentResourceCountMap.end()) {
168 if (found->second > 0) {
169 found->second--;
170 }
171 }
172 }
173}
174
175void ResourceManagerMetrics::notifyClientStarted(const ClientConfigParcel& clientConfig) {
176 std::scoped_lock lock(mLock);
177 int pid = clientConfig.clientInfo.pid;
178 // We need to observer this process.
179 mUidObserver->add(pid, clientConfig.clientInfo.uid);
180
181 // Update the client config for thic client.
182 mClientConfigMap[clientConfig.clientInfo.id] = clientConfig;
183
184 // Update the concurrent codec count for this process.
185 CodecBucket codecBucket = getCodecBucket(clientConfig.isHardware,
186 clientConfig.isEncoder,
187 clientConfig.codecType);
188 increaseConcurrentCodecs(pid, codecBucket);
189
190 if (clientConfig.codecType == MediaResourceSubType::kVideoCodec ||
191 clientConfig.codecType == MediaResourceSubType::kImageCodec) {
192 // Update the pixel count for this process
193 increasePixelCount(pid, clientConfig.width * (long)clientConfig.height);
194 }
195
196 // System concurrent codec usage
197 int systemConcurrentCodecCount = mConcurrentCodecsMap[codecBucket];
198 // Process/Application concurrent codec usage for this type of codec
199 int appConcurrentCodecCount = mProcessConcurrentCodecsMap[pid].mCurrent[codecBucket];
200 // Process/Application's current pixel count.
201 long pixelCount = 0;
202 std::map<int32_t, PixelCount>::iterator it = mProcessPixelsMap.find(pid);
203 if (it != mProcessPixelsMap.end()) {
204 pixelCount = it->second.mCurrent;
205 }
206
207 int result = stats_write(
208 MEDIA_CODEC_STARTED,
209 clientConfig.clientInfo.uid,
210 clientConfig.id,
211 clientConfig.clientInfo.name.c_str(),
212 static_cast<int32_t>(clientConfig.codecType),
213 clientConfig.isEncoder,
214 clientConfig.isHardware,
215 clientConfig.width, clientConfig.height,
216 systemConcurrentCodecCount,
217 appConcurrentCodecCount,
218 pixelCount);
219
220 ALOGV("%s: Pushed MEDIA_CODEC_STARTED atom: "
221 "Process[pid(%d): uid(%d)] "
222 "Codec: [%s: %ju] is %s %s %s "
223 "Timestamp: %jd "
224 "Resolution: %d x %d "
225 "ConcurrentCodec[%d]={System: %d App: %d} "
226 "result: %d",
227 __func__,
228 pid, clientConfig.clientInfo.uid,
229 clientConfig.clientInfo.name.c_str(),
230 clientConfig.id,
231 clientConfig.isHardware? "hardware" : "software",
232 getCodecType(clientConfig.codecType),
233 clientConfig.isEncoder? "encoder" : "decoder",
234 clientConfig.timeStamp,
235 clientConfig.width, clientConfig.height,
236 codecBucket, systemConcurrentCodecCount, appConcurrentCodecCount,
237 result);
238}
239
240void ResourceManagerMetrics::notifyClientStopped(const ClientConfigParcel& clientConfig) {
241 std::scoped_lock lock(mLock);
242 int pid = clientConfig.clientInfo.pid;
243 // Update the concurrent codec count for this process.
244 CodecBucket codecBucket = getCodecBucket(clientConfig.isHardware,
245 clientConfig.isEncoder,
246 clientConfig.codecType);
247 decreaseConcurrentCodecs(pid, codecBucket);
248
249 if (clientConfig.codecType == MediaResourceSubType::kVideoCodec ||
250 clientConfig.codecType == MediaResourceSubType::kImageCodec) {
251 // Update the pixel count for this process
252 decreasePixelCount(pid, clientConfig.width * (long)clientConfig.height);
253 }
254
255 // System concurrent codec usage
256 int systemConcurrentCodecCount = mConcurrentCodecsMap[codecBucket];
257 // Process/Application concurrent codec usage for this type of codec
258 int appConcurrentCodecCount = 0;
259 std::map<int32_t, ConcurrentCodecs>::iterator found = mProcessConcurrentCodecsMap.find(pid);
260 if (found != mProcessConcurrentCodecsMap.end()) {
261 appConcurrentCodecCount = found->second.mCurrent[codecBucket];
262 }
263 // Process/Application's current pixel count.
264 long pixelCount = 0;
265 std::map<int32_t, PixelCount>::iterator it = mProcessPixelsMap.find(pid);
266 if (it != mProcessPixelsMap.end()) {
267 pixelCount = it->second.mCurrent;
268 }
269
270 // calculate the usageTime as:
271 // MediaCodecStopped.clientConfig.timeStamp -
272 // MediaCodecStarted.clientConfig.timeStamp
273 int64_t usageTime = 0;
274 ClientConfigMap::iterator entry = mClientConfigMap.find(clientConfig.clientInfo.id);
275 if (entry != mClientConfigMap.end()) {
276 usageTime = clientConfig.timeStamp - entry->second.timeStamp;
277 // And we can erase this config now.
278 mClientConfigMap.erase(entry);
279 } else {
280 ALOGW("%s: Start Config is missing!", __func__);
281 }
282
283 int result = stats_write(
284 MEDIA_CODEC_STOPPED,
285 clientConfig.clientInfo.uid,
286 clientConfig.id,
287 clientConfig.clientInfo.name.c_str(),
288 static_cast<int32_t>(clientConfig.codecType),
289 clientConfig.isEncoder,
290 clientConfig.isHardware,
291 clientConfig.width, clientConfig.height,
292 systemConcurrentCodecCount,
293 appConcurrentCodecCount,
294 pixelCount,
295 usageTime);
296 ALOGV("%s: Pushed MEDIA_CODEC_STOPPED atom: "
297 "Process[pid(%d): uid(%d)] "
298 "Codec: [%s: %ju] is %s %s %s "
299 "Timestamp: %jd Usage time: %jd "
300 "Resolution: %d x %d "
301 "ConcurrentCodec[%d]={System: %d App: %d} "
302 "result: %d",
303 __func__,
304 pid, clientConfig.clientInfo.uid,
305 clientConfig.clientInfo.name.c_str(),
306 clientConfig.id,
307 clientConfig.isHardware? "hardware" : "software",
308 getCodecType(clientConfig.codecType),
309 clientConfig.isEncoder? "encoder" : "decoder",
310 clientConfig.timeStamp, usageTime,
311 clientConfig.width, clientConfig.height,
312 codecBucket, systemConcurrentCodecCount, appConcurrentCodecCount,
313 result);
314}
315
316void ResourceManagerMetrics::onProcessTerminated(int32_t pid, uid_t uid) {
317 std::scoped_lock lock(mLock);
318 // post MediaCodecConcurrentUsageReported for this terminated pid.
319 pushConcurrentUsageReport(pid, uid);
320}
321
322void ResourceManagerMetrics::pushConcurrentUsageReport(int32_t pid, uid_t uid) {
323 // Process/Application peak concurrent codec usage
324 std::map<int32_t, ConcurrentCodecs>::iterator found = mProcessConcurrentCodecsMap.find(pid);
325 if (found == mProcessConcurrentCodecsMap.end()) {
326 ALOGI("%s: No MEDIA_CODEC_CONCURRENT_USAGE_REPORTED atom Entry for: "
327 "Application[pid(%d): uid(%d)]", __func__, pid, uid);
328 return;
329 }
330 const ConcurrentCodecsMap& codecsMap = found->second.mPeak;
331 int peakHwAudioEncoderCount = codecsMap[HwAudioEncoder];
332 int peakHwAudioDecoderCount = codecsMap[HwAudioDecoder];
333 int peakHwVideoEncoderCount = codecsMap[HwVideoEncoder];
334 int peakHwVideoDecoderCount = codecsMap[HwVideoDecoder];
335 int peakHwImageEncoderCount = codecsMap[HwImageEncoder];
336 int peakHwImageDecoderCount = codecsMap[HwImageDecoder];
337 int peakSwAudioEncoderCount = codecsMap[SwAudioEncoder];
338 int peakSwAudioDecoderCount = codecsMap[SwAudioDecoder];
339 int peakSwVideoEncoderCount = codecsMap[SwVideoEncoder];
340 int peakSwVideoDecoderCount = codecsMap[SwVideoDecoder];
341 int peakSwImageEncoderCount = codecsMap[SwImageEncoder];
342 int peakSwImageDecoderCount = codecsMap[SwImageDecoder];
343
344 long peakPixels = 0;
345 std::map<int32_t, PixelCount>::iterator it = mProcessPixelsMap.find(pid);
346 if (it == mProcessPixelsMap.end()) {
347 ALOGI("%s: No Video Codec Entry for Application[pid(%d): uid(%d)]",
348 __func__, pid, uid);
349 } else {
350 peakPixels = it->second.mPeak;
351 }
352 std::string peakPixelsLog("Peak Pixels: " + std::to_string(peakPixels));
353
354 std::stringstream peakCodecLog;
355 peakCodecLog << "Peak { ";
356 std::stringstream logMsg;
357 if (getLogMessage(peakHwAudioEncoderCount, peakSwAudioEncoderCount, logMsg)) {
358 peakCodecLog << "AudioEnc[" << logMsg.str();
359 }
360 if (getLogMessage(peakHwAudioDecoderCount, peakSwAudioDecoderCount, logMsg)) {
361 peakCodecLog << "AudioDec[" << logMsg.str();
362 }
363 if (getLogMessage(peakHwVideoEncoderCount, peakSwVideoEncoderCount, logMsg)) {
364 peakCodecLog << "VideoEnc[" << logMsg.str();
365 }
366 if (getLogMessage(peakHwVideoDecoderCount, peakSwVideoDecoderCount, logMsg)) {
367 peakCodecLog << "VideoDec[" << logMsg.str();
368 }
369 if (getLogMessage(peakHwImageEncoderCount, peakSwImageEncoderCount, logMsg)) {
370 peakCodecLog << "ImageEnc[" << logMsg.str();
371 }
372 if (getLogMessage(peakHwImageDecoderCount, peakSwImageDecoderCount, logMsg)) {
373 peakCodecLog << "ImageDec[" << logMsg.str();
374 }
375 peakCodecLog << "}";
376
377#ifdef ENABLE_MEDIA_CODEC_CONCURRENT_USAGE_REPORTED
378 int result = stats_write(
379 MEDIA_CODEC_CONCURRENT_USAGE_REPORTED,
380 uid,
381 peakHwVideoDecoderCount,
382 peakHwVideoEncoderCount,
383 peakSwVideoDecoderCount,
384 peakSwVideoEncoderCount,
385 peakHwAudioDecoderCount,
386 peakHwAudioEncoderCount,
387 peakSwAudioDecoderCount,
388 peakSwAudioEncoderCount,
389 peakHwImageDecoderCount,
390 peakHwImageEncoderCount,
391 peakSwImageDecoderCount,
392 peakSwImageEncoderCount,
393 peakPixels);
394 ALOGI("%s: Pushed MEDIA_CODEC_CONCURRENT_USAGE_REPORTED atom: "
395 "Process[pid(%d): uid(%d)] %s %s result: %d",
396 __func__, pid, uid, peakCodecLog.str().c_str(), peakPixelsLog.c_str(), result);
397#else
398 ALOGI("%s: Concurrent Codec Usage Report for the Process[pid(%d): uid(%d)] is %s %s",
399 __func__, pid, uid, peakCodecLog.str().c_str(), peakPixelsLog.c_str());
400#endif
401}
402
403void ResourceManagerMetrics::pushReclaimAtom(const ClientInfoParcel& clientInfo,
404 const std::vector<int>& priorities,
405 const Vector<std::shared_ptr<IResourceManagerClient>>& clients,
406 const PidUidVector& idList, bool reclaimed) {
407 // Construct the metrics for codec reclaim as a pushed atom.
408 // 1. Information about the requester.
409 // - UID and the priority (oom score)
410 int32_t callingPid = clientInfo.pid;
411 int32_t requesterUid = clientInfo.uid;
412 std::string clientName = clientInfo.name;
413 int requesterPriority = priorities[0];
414
415 // 2. Information about the codec.
416 // - Name of the codec requested
417 // - Number of concurrent codecs running.
418 int32_t noOfConcurrentCodecs = 0;
419 std::map<std::string, int>::iterator found = mConcurrentResourceCountMap.find(clientName);
420 if (found != mConcurrentResourceCountMap.end()) {
421 noOfConcurrentCodecs = found->second;
422 }
423
424 // 3. Information about the Reclaim:
425 // - Status of reclaim request
426 // - How many codecs are reclaimed
427 // - For each codecs reclaimed, information of the process that it belonged to:
428 // - UID and the Priority (oom score)
429 int32_t reclaimStatus = MEDIA_CODEC_RECLAIM_REQUEST_COMPLETED__RECLAIM_STATUS__RECLAIM_SUCCESS;
430 if (!reclaimed) {
431 if (clients.size() == 0) {
432 // No clients to reclaim from
433 reclaimStatus =
434 MEDIA_CODEC_RECLAIM_REQUEST_COMPLETED__RECLAIM_STATUS__RECLAIM_FAILED_NO_CLIENTS;
435 } else {
436 // Couldn't reclaim resources from the clients
437 reclaimStatus =
438 MEDIA_CODEC_RECLAIM_REQUEST_COMPLETED__RECLAIM_STATUS__RECLAIM_FAILED_RECLAIM_RESOURCES;
439 }
440 }
441 int32_t noOfCodecsReclaimed = clients.size();
442 int32_t targetIndex = 1;
443 for (PidUidVector::const_reference id : idList) {
444 int32_t targetUid = id.second;
445 int targetPriority = priorities[targetIndex];
446 // Post the pushed atom
447 int result = stats_write(
448 MEDIA_CODEC_RECLAIM_REQUEST_COMPLETED,
449 requesterUid,
450 requesterPriority,
451 clientName.c_str(),
452 noOfConcurrentCodecs,
453 reclaimStatus,
454 noOfCodecsReclaimed,
455 targetIndex,
456 targetUid,
457 targetPriority);
458 ALOGI("%s: Pushed MEDIA_CODEC_RECLAIM_REQUEST_COMPLETED atom: "
459 "Requester[pid(%d): uid(%d): priority(%d)] "
460 "Codec: [%s] "
461 "No of concurrent codecs: %d "
462 "Reclaim Status: %d "
463 "No of codecs reclaimed: %d "
464 "Target[%d][pid(%d): uid(%d): priority(%d)] result: %d",
465 __func__, callingPid, requesterUid, requesterPriority,
466 clientName.c_str(), noOfConcurrentCodecs,
467 reclaimStatus, noOfCodecsReclaimed,
468 targetIndex, id.first, targetUid, targetPriority, result);
469 targetIndex++;
470 }
471}
472
473void ResourceManagerMetrics::increaseConcurrentCodecs(int32_t pid,
474 CodecBucket codecBucket) {
475 // Increase the codec usage across the system.
476 mConcurrentCodecsMap[codecBucket]++;
477
478 // Now update the codec usage for this (pid) process.
479 std::map<int32_t, ConcurrentCodecs>::iterator found = mProcessConcurrentCodecsMap.find(pid);
480 if (found == mProcessConcurrentCodecsMap.end()) {
481 ConcurrentCodecs codecs;
482 codecs.mCurrent[codecBucket] = 1;
483 codecs.mPeak[codecBucket] = 1;
484 mProcessConcurrentCodecsMap.emplace(pid, codecs);
485 } else {
486 found->second.mCurrent[codecBucket]++;
487 // Check if it's the peak count for this slot.
488 if (found->second.mPeak[codecBucket] < found->second.mCurrent[codecBucket]) {
489 found->second.mPeak[codecBucket] = found->second.mCurrent[codecBucket];
490 }
491 }
492}
493
494void ResourceManagerMetrics::decreaseConcurrentCodecs(int32_t pid,
495 CodecBucket codecBucket) {
496 // Decrease the codec usage across the system.
497 if (mConcurrentCodecsMap[codecBucket] > 0) {
498 mConcurrentCodecsMap[codecBucket]--;
499 }
500
501 // Now update the codec usage for this (pid) process.
502 std::map<int32_t, ConcurrentCodecs>::iterator found = mProcessConcurrentCodecsMap.find(pid);
503 if (found != mProcessConcurrentCodecsMap.end()) {
504 if (found->second.mCurrent[codecBucket] > 0) {
505 found->second.mCurrent[codecBucket]--;
506 }
507 }
508}
509
510void ResourceManagerMetrics::increasePixelCount(int32_t pid, long pixels) {
511 // Now update the current pixel usage for this (pid) process.
512 std::map<int32_t, PixelCount>::iterator found = mProcessPixelsMap.find(pid);
513 if (found == mProcessPixelsMap.end()) {
514 PixelCount pixelCount {pixels, pixels};
515 mProcessPixelsMap.emplace(pid, pixelCount);
516 } else {
517 if (__builtin_add_overflow(found->second.mCurrent, pixels, &found->second.mCurrent)) {
518 ALOGI("Pixel Count overflow");
519 return;
520 }
521 // Check if it's the peak count for this slot.
522 if (found->second.mPeak < found->second.mCurrent) {
523 found->second.mPeak = found->second.mCurrent;
524 }
525 }
526}
527
528void ResourceManagerMetrics::decreasePixelCount(int32_t pid, long pixels) {
529 // Now update the current pixel usage for this (pid) process.
530 std::map<int32_t, PixelCount>::iterator found = mProcessPixelsMap.find(pid);
531 if (found != mProcessPixelsMap.end()) {
532 if (found->second.mCurrent < pixels) {
533 found->second.mCurrent = 0;
534 } else {
535 if (__builtin_sub_overflow(found->second.mCurrent, pixels, &found->second.mCurrent)) {
536 ALOGI("Pixel Count overflow");
537 return;
538 }
539 }
540 }
541}
542
543long ResourceManagerMetrics::getPeakConcurrentPixelCount(int pid) const {
544 std::map<int32_t, PixelCount>::const_iterator found = mProcessPixelsMap.find(pid);
545 if (found != mProcessPixelsMap.end()) {
546 return found->second.mPeak;
547 }
548
549 return 0;
550}
551
552long ResourceManagerMetrics::getCurrentConcurrentPixelCount(int pid) const {
553 std::map<int32_t, PixelCount>::const_iterator found = mProcessPixelsMap.find(pid);
554 if (found != mProcessPixelsMap.end()) {
555 return found->second.mCurrent;
556 }
557
558 return 0;
559}
560
561} // namespace android