Create Font sets from native fonts

Bug: 173752727
Test: atest SystemFontsTest (with enabling LAZY flag)
Change-Id: Ic4e405a68e355919ee6493e51528fd44d79cd48e
diff --git a/graphics/java/android/graphics/fonts/Font.java b/graphics/java/android/graphics/fonts/Font.java
index e7c1582..fea756c 100644
--- a/graphics/java/android/graphics/fonts/Font.java
+++ b/graphics/java/android/graphics/fonts/Font.java
@@ -124,6 +124,19 @@
         }
 
         /**
+         * Construct a builder with a byte buffer and file path.
+         *
+         * This method is intended to be called only from SystemFonts.
+         * @param path font file path
+         * @param localeList comma concatenated BCP47 compliant language tag.
+         * @hide
+         */
+        public Builder(@NonNull File path, @NonNull String localeList) {
+            this(path);
+            mLocaleList = localeList;
+        }
+
+        /**
          * Constructs a builder with a file path.
          *
          * @param path a file path to the font file
@@ -809,29 +822,13 @@
 
             // If not found, create Font object from native object for Java API users.
             ByteBuffer buffer = NativeFontBufferHelper.refByteBuffer(ptr);
-            long packed = nGetFontInfo(ptr);
-            int weight = (int) (packed & 0x0000_0000_0000_FFFFL);
-            boolean italic = (packed & 0x0000_0000_0001_0000L) != 0;
-            int ttcIndex = (int) ((packed & 0x0000_FFFF_0000_0000L) >> 32);
-            int axisCount = (int) ((packed & 0xFFFF_0000_0000_0000L) >> 48);
-            FontVariationAxis[] axes = new FontVariationAxis[axisCount];
-            char[] charBuffer = new char[4];
-            for (int i = 0; i < axisCount; ++i) {
-                long packedAxis = nGetAxisInfo(ptr, i);
-                float value = Float.intBitsToFloat((int) (packedAxis & 0x0000_0000_FFFF_FFFFL));
-                charBuffer[0] = (char) ((packedAxis & 0xFF00_0000_0000_0000L) >> 56);
-                charBuffer[1] = (char) ((packedAxis & 0x00FF_0000_0000_0000L) >> 48);
-                charBuffer[2] = (char) ((packedAxis & 0x0000_FF00_0000_0000L) >> 40);
-                charBuffer[3] = (char) ((packedAxis & 0x0000_00FF_0000_0000L) >> 32);
-                axes[i] = new FontVariationAxis(new String(charBuffer), value);
-            }
-            String path = nGetFontPath(ptr);
-            File file = (path == null) ? null : new File(path);
-            Font.Builder builder = new Font.Builder(buffer, file, "")
-                    .setWeight(weight)
-                    .setSlant(italic ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT)
-                    .setTtcIndex(ttcIndex)
-                    .setFontVariationSettings(axes);
+            NativeFont.Font font = NativeFont.readNativeFont(ptr);
+
+            Font.Builder builder = new Font.Builder(buffer, font.getFile(), "")
+                    .setWeight(font.getStyle().getWeight())
+                    .setSlant(font.getStyle().getSlant())
+                    .setTtcIndex(font.getIndex())
+                    .setFontVariationSettings(font.getAxes());
 
             Font newFont = null;
             try {
@@ -845,15 +842,6 @@
         }
     }
 
-    @CriticalNative
-    private static native long nGetFontInfo(long ptr);
-
-    @CriticalNative
-    private static native long nGetAxisInfo(long ptr, int i);
-
-    @FastNative
-    private static native String nGetFontPath(long ptr);
-
     @FastNative
     private static native float nGetGlyphBounds(long font, int glyphId, long paint, RectF rect);
 
diff --git a/graphics/java/android/graphics/fonts/NativeFont.java b/graphics/java/android/graphics/fonts/NativeFont.java
new file mode 100644
index 0000000..9e9d76a
--- /dev/null
+++ b/graphics/java/android/graphics/fonts/NativeFont.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics.fonts;
+
+import android.graphics.Typeface;
+
+import dalvik.annotation.optimization.CriticalNative;
+import dalvik.annotation.optimization.FastNative;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Read native font objects.
+ *
+ * @hide
+ */
+public class NativeFont {
+
+    /**
+     * Represents native font object.
+     */
+    public static final class Font {
+        private final File mFile;
+        private final int mIndex;
+        private final FontVariationAxis[] mAxes;
+        private final FontStyle mStyle;
+
+        public Font(File file, int index, FontVariationAxis[] axes, FontStyle style) {
+            mFile = file;
+            mIndex = index;
+            mAxes = axes;
+            mStyle = style;
+        }
+
+        public File getFile() {
+            return mFile;
+        }
+
+        public FontVariationAxis[] getAxes() {
+            return mAxes;
+        }
+
+        public FontStyle getStyle() {
+            return mStyle;
+        }
+
+        public int getIndex() {
+            return mIndex;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            Font font = (Font) o;
+            return mIndex == font.mIndex && mFile.equals(font.mFile)
+                    && Arrays.equals(mAxes, font.mAxes) && mStyle.equals(font.mStyle);
+        }
+
+        @Override
+        public int hashCode() {
+            int result = Objects.hash(mFile, mIndex, mStyle);
+            result = 31 * result + Arrays.hashCode(mAxes);
+            return result;
+        }
+    }
+
+    /**
+     * Represents native font family object.
+     */
+    public static final class Family {
+        private final List<Font> mFonts;
+        private final String mLocale;
+
+        public Family(List<Font> fonts, String locale) {
+            mFonts = fonts;
+            mLocale = locale;
+        }
+
+        public List<Font> getFonts() {
+            return mFonts;
+        }
+
+        public String getLocale() {
+            return mLocale;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            Family family = (Family) o;
+            return mFonts.equals(family.mFonts) && mLocale.equals(family.mLocale);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mFonts, mLocale);
+        }
+    }
+
+    /**
+     * Get underlying font families from Typeface
+     *
+     * @param typeface a typeface
+     * @return list of family
+     */
+    public static List<Family> readTypeface(Typeface typeface) {
+        int familyCount = nGetFamilyCount(typeface.native_instance);
+        List<Family> result = new ArrayList<>(familyCount);
+        for (int i = 0; i < familyCount; ++i) {
+            result.add(readNativeFamily(nGetFamily(typeface.native_instance, i)));
+        }
+        return result;
+    }
+
+    /**
+     * Read family object from native pointer
+     *
+     * @param familyPtr a font family pointer
+     * @return a family
+     */
+    public static Family readNativeFamily(long familyPtr) {
+        int fontCount = nGetFontCount(familyPtr);
+        List<Font> result = new ArrayList<>(fontCount);
+        for (int i = 0; i < fontCount; ++i) {
+            result.add(readNativeFont(nGetFont(familyPtr, i)));
+        }
+        String localeList = nGetLocaleList(familyPtr);
+        return new Family(result, localeList);
+    }
+
+    /**
+     * Read font object from native pointer.
+     *
+     * @param ptr a font pointer
+     * @return a font
+     */
+    public static Font readNativeFont(long ptr) {
+        long packed = nGetFontInfo(ptr);
+        int weight = (int) (packed & 0x0000_0000_0000_FFFFL);
+        boolean italic = (packed & 0x0000_0000_0001_0000L) != 0;
+        int ttcIndex = (int) ((packed & 0x0000_FFFF_0000_0000L) >> 32);
+        int axisCount = (int) ((packed & 0xFFFF_0000_0000_0000L) >> 48);
+        FontVariationAxis[] axes = new FontVariationAxis[axisCount];
+        char[] charBuffer = new char[4];
+        for (int i = 0; i < axisCount; ++i) {
+            long packedAxis = nGetAxisInfo(ptr, i);
+            float value = Float.intBitsToFloat((int) (packedAxis & 0x0000_0000_FFFF_FFFFL));
+            charBuffer[0] = (char) ((packedAxis & 0xFF00_0000_0000_0000L) >> 56);
+            charBuffer[1] = (char) ((packedAxis & 0x00FF_0000_0000_0000L) >> 48);
+            charBuffer[2] = (char) ((packedAxis & 0x0000_FF00_0000_0000L) >> 40);
+            charBuffer[3] = (char) ((packedAxis & 0x0000_00FF_0000_0000L) >> 32);
+            axes[i] = new FontVariationAxis(new String(charBuffer), value);
+        }
+        String path = nGetFontPath(ptr);
+        File file = (path == null) ? null : new File(path);
+        FontStyle style = new FontStyle(weight,
+                italic ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT);
+
+        return new Font(file, ttcIndex, axes, style);
+    }
+
+    @CriticalNative
+    private static native int nGetFamilyCount(long ptr);
+
+    @CriticalNative
+    private static native long nGetFamily(long ptr, int index);
+
+    @FastNative
+    private static native String nGetLocaleList(long familyPtr);
+
+    @CriticalNative
+    private static native long nGetFont(long familyPtr, int fontIndex);
+
+    @CriticalNative
+    private static native int nGetFontCount(long familyPtr);
+
+    @CriticalNative
+    private static native long nGetFontInfo(long fontPtr);
+
+    @CriticalNative
+    private static native long nGetAxisInfo(long fontPtr, int i);
+
+    @FastNative
+    private static native String nGetFontPath(long fontPtr);
+}
diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java
index 3635adc..28943ba 100644
--- a/graphics/java/android/graphics/fonts/SystemFonts.java
+++ b/graphics/java/android/graphics/fonts/SystemFonts.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.graphics.FontListParser;
+import android.graphics.Typeface;
 import android.text.FontConfig;
 import android.util.ArrayMap;
 import android.util.Log;
@@ -68,19 +69,50 @@
                 return sAvailableFonts;
             }
 
-            Set<Font> set = new HashSet<>();
+            if (Typeface.ENABLE_LAZY_TYPEFACE_INITIALIZATION) {
+                sAvailableFonts = collectAllFonts();
+            } else {
+                Set<Font> set = new HashSet<>();
+                for (FontFamily[] items : sFamilyMap.values()) {
+                    for (FontFamily family : items) {
+                        for (int i = 0; i < family.getSize(); ++i) {
+                            set.add(family.getFont(i));
+                        }
+                    }
+                }
 
-            for (FontFamily[] items : sFamilyMap.values()) {
-                for (FontFamily family : items) {
-                    for (int i = 0; i < family.getSize(); ++i) {
-                        set.add(family.getFont(i));
+                sAvailableFonts = Collections.unmodifiableSet(set);
+            }
+            return sAvailableFonts;
+        }
+    }
+
+    private static @NonNull Set<Font> collectAllFonts() {
+        Map<String, Typeface> map = Typeface.getSystemFontMap();
+        HashSet<NativeFont.Font> seenFonts = new HashSet<>();
+        HashSet<Font> result = new HashSet<>();
+        for (Typeface typeface : map.values()) {
+            List<NativeFont.Family> families = NativeFont.readTypeface(typeface);
+            for (NativeFont.Family family : families) {
+                for (NativeFont.Font font : family.getFonts()) {
+                    if (seenFonts.contains(font)) {
+                        continue;
+                    }
+                    seenFonts.add(font);
+                    try {
+                        result.add(new Font.Builder(font.getFile(), family.getLocale())
+                                .setFontVariationSettings(font.getAxes())
+                                .setTtcIndex(font.getIndex())
+                                .setWeight(font.getStyle().getWeight())
+                                .setSlant(font.getStyle().getSlant())
+                                .build());
+                    } catch (IOException e) {
+                        Log.w(TAG, "Failed to load " + font.getFile(), e);
                     }
                 }
             }
-
-            sAvailableFonts = Collections.unmodifiableSet(set);
-            return sAvailableFonts;
         }
+        return result;
     }
 
     private static @Nullable ByteBuffer mmap(@NonNull String fullPath) {
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index cd53217..1ff1978 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -333,6 +333,7 @@
         "jni/YuvToJpegEncoder.cpp",
         "jni/fonts/Font.cpp",
         "jni/fonts/FontFamily.cpp",
+        "jni/fonts/NativeFont.cpp",
         "jni/text/LineBreaker.cpp",
         "jni/text/MeasuredText.cpp",
         "jni/text/TextShaper.cpp",
diff --git a/libs/hwui/apex/jni_runtime.cpp b/libs/hwui/apex/jni_runtime.cpp
index e1f5abd7..0fad2d5 100644
--- a/libs/hwui/apex/jni_runtime.cpp
+++ b/libs/hwui/apex/jni_runtime.cpp
@@ -69,6 +69,7 @@
 extern int register_android_graphics_drawable_VectorDrawable(JNIEnv* env);
 extern int register_android_graphics_fonts_Font(JNIEnv* env);
 extern int register_android_graphics_fonts_FontFamily(JNIEnv* env);
+extern int register_android_graphics_fonts_NativeFont(JNIEnv* env);
 extern int register_android_graphics_pdf_PdfDocument(JNIEnv* env);
 extern int register_android_graphics_pdf_PdfEditor(JNIEnv* env);
 extern int register_android_graphics_pdf_PdfRenderer(JNIEnv* env);
@@ -135,6 +136,7 @@
     REG_JNI(register_android_graphics_drawable_VectorDrawable),
     REG_JNI(register_android_graphics_fonts_Font),
     REG_JNI(register_android_graphics_fonts_FontFamily),
+    REG_JNI(register_android_graphics_fonts_NativeFont),
     REG_JNI(register_android_graphics_pdf_PdfDocument),
     REG_JNI(register_android_graphics_pdf_PdfEditor),
     REG_JNI(register_android_graphics_pdf_PdfRenderer),
diff --git a/libs/hwui/jni/fonts/Font.cpp b/libs/hwui/jni/fonts/Font.cpp
index f0c7793..f612bce 100644
--- a/libs/hwui/jni/fonts/Font.cpp
+++ b/libs/hwui/jni/fonts/Font.cpp
@@ -187,38 +187,6 @@
 }
 
 // Critical Native
-static jlong Font_getFontInfo(CRITICAL_JNI_PARAMS_COMMA jlong fontHandle) {
-    const minikin::Font* font = reinterpret_cast<minikin::Font*>(fontHandle);
-    MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->typeface().get());
-
-    uint64_t result = font->style().weight();
-    result |= font->style().slant() == minikin::FontStyle::Slant::ITALIC ? 0x10000 : 0x00000;
-    result |= ((static_cast<uint64_t>(minikinSkia->GetFontIndex())) << 32);
-    result |= ((static_cast<uint64_t>(minikinSkia->GetAxes().size())) << 48);
-    return result;
-}
-
-// Critical Native
-static jlong Font_getAxisInfo(CRITICAL_JNI_PARAMS_COMMA jlong fontHandle, jint index) {
-    const minikin::Font* font = reinterpret_cast<minikin::Font*>(fontHandle);
-    MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->typeface().get());
-    const minikin::FontVariation& var = minikinSkia->GetAxes().at(index);
-    uint32_t floatBinary = *reinterpret_cast<const uint32_t*>(&var.value);
-    return (static_cast<uint64_t>(var.axisTag) << 32) | static_cast<uint64_t>(floatBinary);
-}
-
-// FastNative
-static jstring Font_getFontPath(JNIEnv* env, jobject, jlong fontHandle) {
-    const minikin::Font* font = reinterpret_cast<minikin::Font*>(fontHandle);
-    MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->typeface().get());
-    const std::string& filePath = minikinSkia->getFilePath();
-    if (filePath.empty()) {
-        return nullptr;
-    }
-    return env->NewStringUTF(filePath.c_str());
-}
-
-// Critical Native
 static jlong Font_getNativeFontPtr(CRITICAL_JNI_PARAMS_COMMA jlong fontHandle) {
     FontWrapper* font = reinterpret_cast<FontWrapper*>(fontHandle);
     return reinterpret_cast<jlong>(font->font.get());
@@ -276,9 +244,6 @@
 static const JNINativeMethod gFontMethods[] = {
     { "nGetGlyphBounds", "(JIJLandroid/graphics/RectF;)F", (void*) Font_getGlyphBounds },
     { "nGetFontMetrics", "(JJLandroid/graphics/Paint$FontMetrics;)F", (void*) Font_getFontMetrics },
-    { "nGetFontInfo", "(J)J", (void*) Font_getFontInfo },
-    { "nGetAxisInfo", "(JI)J", (void*) Font_getAxisInfo },
-    { "nGetFontPath", "(J)Ljava/lang/String;", (void*) Font_getFontPath },
     { "nGetNativeFontPtr", "(J)J", (void*) Font_getNativeFontPtr },
     { "nGetFontBufferAddress", "(J)J", (void*) Font_GetBufferAddress },
 };
diff --git a/libs/hwui/jni/fonts/NativeFont.cpp b/libs/hwui/jni/fonts/NativeFont.cpp
new file mode 100644
index 0000000..c5c5d46
--- /dev/null
+++ b/libs/hwui/jni/fonts/NativeFont.cpp
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "Minikin"
+
+#include "Font.h"
+#include "SkData.h"
+#include "SkFont.h"
+#include "SkFontMetrics.h"
+#include "SkFontMgr.h"
+#include "SkRefCnt.h"
+#include "SkTypeface.h"
+#include "GraphicsJNI.h"
+#include <nativehelper/ScopedUtfChars.h>
+#include "Utils.h"
+#include "FontUtils.h"
+
+#include <hwui/MinikinSkia.h>
+#include <hwui/Paint.h>
+#include <hwui/Typeface.h>
+#include <minikin/FontFamily.h>
+#include <minikin/LocaleList.h>
+#include <ui/FatVector.h>
+
+#include <memory>
+
+namespace android {
+
+// Critical Native
+static jint NativeFont_getFamilyCount(CRITICAL_JNI_PARAMS_COMMA jlong typefaceHandle) {
+    Typeface* tf = reinterpret_cast<Typeface*>(typefaceHandle);
+    return tf->fFontCollection->getFamilies().size();
+}
+
+// Critical Native
+static jlong NativeFont_getFamily(CRITICAL_JNI_PARAMS_COMMA jlong typefaceHandle, jint index) {
+    Typeface* tf = reinterpret_cast<Typeface*>(typefaceHandle);
+    return reinterpret_cast<jlong>(tf->fFontCollection->getFamilies()[index].get());
+
+}
+
+// Fast Native
+static jstring NativeFont_getLocaleList(JNIEnv* env, jobject, jlong familyHandle) {
+    minikin::FontFamily* family = reinterpret_cast<minikin::FontFamily*>(familyHandle);
+    uint32_t localeListId = family->localeListId();
+    return env->NewStringUTF(minikin::getLocaleString(localeListId).c_str());
+}
+
+// Critical Native
+static jint NativeFont_getFontCount(CRITICAL_JNI_PARAMS_COMMA jlong familyHandle) {
+    minikin::FontFamily* family = reinterpret_cast<minikin::FontFamily*>(familyHandle);
+    return family->getNumFonts();
+}
+
+// Critical Native
+static jlong NativeFont_getFont(CRITICAL_JNI_PARAMS_COMMA jlong familyHandle, jint index) {
+    minikin::FontFamily* family = reinterpret_cast<minikin::FontFamily*>(familyHandle);
+    return reinterpret_cast<jlong>(family->getFont(index));
+}
+
+// Critical Native
+static jlong NativeFont_getFontInfo(CRITICAL_JNI_PARAMS_COMMA jlong fontHandle) {
+    const minikin::Font* font = reinterpret_cast<minikin::Font*>(fontHandle);
+    MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->typeface().get());
+
+    uint64_t result = font->style().weight();
+    result |= font->style().slant() == minikin::FontStyle::Slant::ITALIC ? 0x10000 : 0x00000;
+    result |= ((static_cast<uint64_t>(minikinSkia->GetFontIndex())) << 32);
+    result |= ((static_cast<uint64_t>(minikinSkia->GetAxes().size())) << 48);
+    return result;
+}
+
+// Critical Native
+static jlong NativeFont_getAxisInfo(CRITICAL_JNI_PARAMS_COMMA jlong fontHandle, jint index) {
+    const minikin::Font* font = reinterpret_cast<minikin::Font*>(fontHandle);
+    MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->typeface().get());
+    const minikin::FontVariation& var = minikinSkia->GetAxes().at(index);
+    uint32_t floatBinary = *reinterpret_cast<const uint32_t*>(&var.value);
+    return (static_cast<uint64_t>(var.axisTag) << 32) | static_cast<uint64_t>(floatBinary);
+}
+
+// FastNative
+static jstring NativeFont_getFontPath(JNIEnv* env, jobject, jlong fontHandle) {
+    const minikin::Font* font = reinterpret_cast<minikin::Font*>(fontHandle);
+    MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->typeface().get());
+    const std::string& filePath = minikinSkia->getFilePath();
+    if (filePath.empty()) {
+        return nullptr;
+    }
+    return env->NewStringUTF(filePath.c_str());
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static const JNINativeMethod gNativeFontMethods[] = {
+    { "nGetFamilyCount", "(J)I", (void*) NativeFont_getFamilyCount },
+    { "nGetFamily", "(JI)J", (void*) NativeFont_getFamily },
+    { "nGetLocaleList", "(J)Ljava/lang/String;", (void*) NativeFont_getLocaleList },
+    { "nGetFontCount", "(J)I", (void*) NativeFont_getFontCount },
+    { "nGetFont", "(JI)J", (void*) NativeFont_getFont },
+    { "nGetFontInfo", "(J)J", (void*) NativeFont_getFontInfo },
+    { "nGetAxisInfo", "(JI)J", (void*) NativeFont_getAxisInfo },
+    { "nGetFontPath", "(J)Ljava/lang/String;", (void*) NativeFont_getFontPath },
+};
+
+int register_android_graphics_fonts_NativeFont(JNIEnv* env) {
+    return RegisterMethodsOrDie(env, "android/graphics/fonts/NativeFont", gNativeFontMethods,
+            NELEM(gNativeFontMethods));
+}
+
+}  // namespace android