@@ -58,6 +58,65 @@ test("preserves vector even-odd fill rules", async () => {
5858 assert . match ( svg , / c l i p - r u l e = " e v e n o d d " / u) ;
5959} ) ;
6060
61+ test ( "prefers renderable vector drawables over bitmap app icon candidates" , async ( ) => {
62+ const vectorPath = "res/mipmap-anydpi-v26/ic_launcher.xml" ;
63+ const bitmapPath = "res/mipmap-xxxhdpi/ic_launcher.png" ;
64+ const source = createIconSource ( {
65+ [ vectorPath ] : textEncoder . encode ( `
66+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
67+ android:width="108dp"
68+ android:height="108dp"
69+ android:viewportWidth="24"
70+ android:viewportHeight="24">
71+ <path
72+ android:pathData="M12,2l9,20h-18z"
73+ android:fillColor="#2f80ed"/>
74+ </vector>
75+ ` ) ,
76+ [ bitmapPath ] : createPngHeaderBytes ( ) ,
77+ } ) ;
78+ const icon = await __apkTestInternals . readBestIconCandidate (
79+ source ,
80+ createIconResources ( ) ,
81+ 0x7f010001 ,
82+ [
83+ createIconCandidate ( bitmapPath , 640 ) ,
84+ createIconCandidate ( vectorPath , 0xfffe ) ,
85+ ] ,
86+ ) ;
87+
88+ assert . equal ( icon . mimeType , "image/svg+xml" ) ;
89+ assert . equal ( icon . path , vectorPath ) ;
90+ assert . match ( decodeSvgDataUri ( icon . dataUri ) , / < p a t h / u) ;
91+ } ) ;
92+
93+ test ( "prefers simple shape drawables over bitmap app icon candidates" , async ( ) => {
94+ const shapePath = "res/drawable/ic_launcher.xml" ;
95+ const bitmapPath = "res/drawable-xxxhdpi/ic_launcher.png" ;
96+ const source = createIconSource ( {
97+ [ shapePath ] : textEncoder . encode ( `
98+ <shape xmlns:android="http://schemas.android.com/apk/res/android"
99+ android:shape="rectangle">
100+ <solid android:color="#2f80ed"/>
101+ </shape>
102+ ` ) ,
103+ [ bitmapPath ] : createPngHeaderBytes ( ) ,
104+ } ) ;
105+ const icon = await __apkTestInternals . readBestIconCandidate (
106+ source ,
107+ createIconResources ( ) ,
108+ 0x7f010002 ,
109+ [
110+ createIconCandidate ( bitmapPath , 640 ) ,
111+ createIconCandidate ( shapePath , 0 ) ,
112+ ] ,
113+ ) ;
114+
115+ assert . equal ( icon . mimeType , "image/svg+xml" ) ;
116+ assert . equal ( icon . path , shapePath ) ;
117+ assert . match ( decodeSvgDataUri ( icon . dataUri ) , / f i l l = " # 2 f 8 0 e d " / u) ;
118+ } ) ;
119+
61120test ( "annotates native libraries with ELF page size and ZIP alignment" , async ( ) => {
62121 const elfBytes = createElf64WithLoadAlignment ( 0x4000 ) ;
63122 const zipBytes = createStoredZipWithSingleEntry ( "lib/arm64-v8a/libpage.so" , elfBytes , 4096 ) ;
@@ -81,6 +140,42 @@ function decodeSvgDataUri(dataUri) {
81140 return Buffer . from ( base64 , "base64" ) . toString ( "utf8" ) ;
82141}
83142
143+ function createIconSource ( files ) {
144+ const fileEntries = Object . entries ( files ) . map ( ( [ path , bytes ] ) => [
145+ path ,
146+ {
147+ path,
148+ compressedSize : bytes . byteLength ,
149+ uncompressedSize : bytes . byteLength ,
150+ } ,
151+ ] ) ;
152+ const fileMap = new Map ( Object . entries ( files ) ) ;
153+ return {
154+ zipEntries : new Map ( fileEntries ) ,
155+ extractEntry : async ( entry ) => fileMap . get ( entry . path ) ,
156+ } ;
157+ }
158+
159+ function createIconResources ( ) {
160+ return {
161+ resolveColor : ( ) => null ,
162+ resolveFiles : ( ) => [ ] ,
163+ } ;
164+ }
165+
166+ function createIconCandidate ( path , density ) {
167+ return {
168+ path,
169+ density,
170+ typeName : path . includes ( "/mipmap" ) ? "mipmap" : "drawable" ,
171+ isDefaultConfig : path . includes ( "/mipmap/" ) || path . includes ( "/drawable/" ) ,
172+ } ;
173+ }
174+
175+ function createPngHeaderBytes ( ) {
176+ return new Uint8Array ( [ 0x89 , 0x50 , 0x4e , 0x47 , 0x0d , 0x0a , 0x1a , 0x0a ] ) ;
177+ }
178+
84179function createElf64WithLoadAlignment ( alignment ) {
85180 const bytes = new Uint8Array ( 0x80 ) ;
86181 const view = new DataView ( bytes . buffer ) ;
0 commit comments