| /* |
| * Copyright (C) 2021 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 static android.graphics.fonts.FontStyle.FONT_SLANT_UPRIGHT; |
| import static android.graphics.fonts.FontStyle.FONT_WEIGHT_NORMAL; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| |
| import static org.junit.Assert.fail; |
| |
| import android.Manifest; |
| import android.app.UiAutomation; |
| import android.content.Context; |
| import android.os.ParcelFileDescriptor; |
| import android.text.FontConfig; |
| import android.text.TextUtils; |
| |
| import androidx.test.filters.SmallTest; |
| import androidx.test.platform.app.InstrumentationRegistry; |
| import androidx.test.runner.AndroidJUnit4; |
| |
| import org.junit.Ignore; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| |
| import java.io.BufferedReader; |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| @SmallTest |
| @RunWith(AndroidJUnit4.class) |
| public class FontManagerTest { |
| |
| private Context getContext() { |
| return InstrumentationRegistry.getInstrumentation().getTargetContext(); |
| } |
| |
| private HashSet<String> getFallbackNameSet(FontConfig config) { |
| HashSet<String> fallbackNames = new HashSet<>(); |
| List<FontConfig.FontFamily> families = config.getFontFamilies(); |
| assertThat(families).isNotEmpty(); |
| for (FontConfig.FontFamily family : families) { |
| if (family.getName() != null) { |
| fallbackNames.add(family.getName()); |
| } |
| } |
| return fallbackNames; |
| } |
| |
| private FontConfig getFontConfig() { |
| UiAutomation ui = InstrumentationRegistry.getInstrumentation().getUiAutomation(); |
| |
| ui.adoptShellPermissionIdentity(Manifest.permission.UPDATE_FONTS); |
| try { |
| FontManager fm = getContext().getSystemService(FontManager.class); |
| assertThat(fm).isNotNull(); |
| |
| return fm.getFontConfig(); |
| } finally { |
| ui.dropShellPermissionIdentity(); |
| } |
| } |
| |
| @Test |
| public void fontManager_getFontConfig_checkFamilies() { |
| FontConfig config = getFontConfig(); |
| // To expect name availability, collect all fallback names. |
| Set<String> fallbackNames = getFallbackNameSet(config); |
| |
| List<FontConfig.FontFamily> families = config.getFontFamilies(); |
| assertThat(families).isNotEmpty(); |
| |
| for (FontConfig.FontFamily family : families) { |
| assertThat(family.getFontList()).isNotEmpty(); |
| |
| if (family.getName() != null) { |
| assertThat(family.getName()).isNotEmpty(); |
| } |
| |
| assertThat(family.getLocaleList()).isNotNull(); |
| assertThat(family.getVariant()).isAtLeast(0); |
| assertThat(family.getVariant()).isAtMost(2); |
| |
| List<FontConfig.Font> fonts = family.getFontList(); |
| for (FontConfig.Font font : fonts) { |
| // Provided font files must be readable. |
| assertThat(font.getFile().canRead()).isTrue(); |
| |
| assertThat(font.getTtcIndex()).isAtLeast(0); |
| assertThat(font.getFontVariationSettings()).isNotNull(); |
| assertThat(font.getStyle()).isNotNull(); |
| if (font.getFontFamilyName() != null) { |
| assertThat(font.getFontFamilyName()).isIn(fallbackNames); |
| } |
| } |
| } |
| } |
| |
| @Ignore("TODO(b/199671094)") |
| @Test |
| public void fontManager_getFontConfig_checkAlias() { |
| FontConfig config = getFontConfig(); |
| assertThat(config).isNotNull(); |
| // To expect name availability, collect all fallback names. |
| Set<String> fallbackNames = getFallbackNameSet(config); |
| |
| List<FontConfig.Alias> aliases = config.getAliases(); |
| assertThat(aliases).isNotEmpty(); |
| for (FontConfig.Alias alias : aliases) { |
| assertThat(alias.getName()).isNotEmpty(); |
| assertThat(alias.getOriginal()).isNotEmpty(); |
| assertThat(alias.getWeight()).isAtLeast(0); |
| assertThat(alias.getWeight()).isAtMost(1000); |
| |
| // The alias must be in the existing fallback names |
| assertThat(alias.getOriginal()).isIn(fallbackNames); |
| } |
| } |
| |
| private List<String> readAll(InputStream is) throws IOException { |
| BufferedReader reader = new BufferedReader(new InputStreamReader(is)); |
| ArrayList<String> out = new ArrayList<>(); |
| String line = ""; |
| while ((line = reader.readLine()) != null) { |
| String trimmed = line.trim(); |
| if (!TextUtils.isEmpty(trimmed)) { |
| out.add(trimmed); |
| } |
| } |
| return out; |
| } |
| |
| private void assertSecurityException(String command) throws Exception { |
| Process proc = Runtime.getRuntime().exec(new String[] { "cmd", "font", command }); |
| |
| // The shell command must not success. |
| assertThat(proc.waitFor()).isNotEqualTo(0); |
| |
| // In case of calling from unauthorized UID, must not output anything. |
| assertThat(readAll(proc.getInputStream())).isEmpty(); |
| |
| // Any shell command is not allowed. Output error message and exit immediately. |
| List<String> errors = readAll(proc.getErrorStream()); |
| assertThat(errors).isNotEmpty(); |
| assertThat(errors.get(0)).isEqualTo("Only shell or root user can execute font command."); |
| } |
| |
| private static void updateFontFile(FontManager fm, FontFileUpdateRequest ffur, |
| int baseVersion) { |
| fm.updateFontFamily( |
| new FontFamilyUpdateRequest.Builder().addFontFileUpdateRequest(ffur).build(), |
| baseVersion); |
| } |
| |
| @Test |
| public void fontManager_shellCommandPermissionTest() throws Exception { |
| assertSecurityException(""); |
| assertSecurityException("update"); |
| assertSecurityException("clear"); |
| assertSecurityException("status"); |
| assertSecurityException("random_string"); |
| } |
| |
| @Test |
| public void fontManager_updateFontFile_negativeBaseVersion() throws Exception { |
| FontManager fm = getContext().getSystemService(FontManager.class); |
| assertThat(fm).isNotNull(); |
| |
| // Roboto-Regular.ttf is always available. |
| File robotoFile = new File("/system/fonts/Roboto-Regular.ttf"); |
| ParcelFileDescriptor pfd = ParcelFileDescriptor.open(robotoFile, |
| ParcelFileDescriptor.MODE_READ_ONLY); |
| |
| try { |
| updateFontFile(fm, new FontFileUpdateRequest(pfd, new byte[256]), -1); |
| fail("IllegalArgumentException is expected."); |
| } catch (IllegalArgumentException e) { |
| // pass |
| } |
| } |
| |
| @Test |
| public void fontManager_updateFontFile_permissionEnforce() throws Exception { |
| FontManager fm = getContext().getSystemService(FontManager.class); |
| assertThat(fm).isNotNull(); |
| |
| // Roboto-Regular.ttf is always available. |
| File robotoFile = new File("/system/fonts/Roboto-Regular.ttf"); |
| ParcelFileDescriptor pfd = ParcelFileDescriptor.open(robotoFile, |
| ParcelFileDescriptor.MODE_READ_ONLY); |
| byte[] randomSignature = new byte[256]; |
| |
| try { |
| updateFontFile(fm, new FontFileUpdateRequest(pfd, randomSignature), |
| fm.getFontConfig().getConfigVersion()); |
| fail("SecurityException is expected."); |
| } catch (SecurityException e) { |
| // pass |
| } |
| } |
| |
| @Test |
| public void fontManager_updateFontFamily_negativeBaseVersion() { |
| FontManager fm = getContext().getSystemService(FontManager.class); |
| assertThat(fm).isNotNull(); |
| |
| try { |
| fm.updateFontFamily(new FontFamilyUpdateRequest.Builder() |
| .addFontFamily(new FontFamilyUpdateRequest.FontFamily.Builder("test", |
| Arrays.asList(new FontFamilyUpdateRequest.Font.Builder( |
| "Roboto-Regular", |
| new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT)).build()) |
| ).build()) |
| .build(), -1); |
| fail("IllegalArgumentException is expected."); |
| } catch (IllegalArgumentException e) { |
| // pass |
| } |
| } |
| |
| |
| @Test |
| public void fontManager_updateFontFamily_permissionEnforce() { |
| FontManager fm = getContext().getSystemService(FontManager.class); |
| assertThat(fm).isNotNull(); |
| |
| try { |
| fm.updateFontFamily(new FontFamilyUpdateRequest.Builder() |
| .addFontFamily(new FontFamilyUpdateRequest.FontFamily.Builder("test", |
| Arrays.asList(new FontFamilyUpdateRequest.Font.Builder( |
| "Roboto-Regular", |
| new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT)) |
| .build())).build()) |
| .build(), fm.getFontConfig().getConfigVersion()); |
| fail("SecurityException is expected."); |
| } catch (SecurityException e) { |
| // pass |
| } |
| } |
| |
| @Test |
| public void fontManager_PostScriptName() throws IOException { |
| FontConfig fontConfig = getFontConfig(); |
| for (FontConfig.FontFamily family : fontConfig.getFontFamilies()) { |
| for (FontConfig.Font font : family.getFontList()) { |
| String psNameInFile = FontFileTestUtil.getPostScriptName(font.getFile()); |
| assertThat(font.getPostScriptName()).isEqualTo(psNameInFile); |
| } |
| } |
| } |
| |
| // TODO: Add more tests once we sign test fonts. |
| } |