comparison android/DWindows.kt @ 2658:ad6fc7f1a9af

Android: Asset/Resource refactoring for compatibility with other platforms. Other platforms are able to access the bundled assets via the filesystem. Android keeps the assets in the Zipped APK archive, so during initialization we now extract the non-resource (numeric images) into the app cache dir. dw_app_dir() now returns the path to the application cache. dw_user_dir() returns either the HOME directory or the path to the application root directory (usually the parent of the cache).
author bsmith@81767d24-ef19-dc11-ae90-00e081727c95
date Fri, 24 Sep 2021 13:57:16 +0000
parents 2bdbd5e83654
children 3a14d7fd4b99
comparison
equal deleted inserted replaced
2657:9535f533a230 2658:ad6fc7f1a9af
51 import androidx.recyclerview.widget.RecyclerView 51 import androidx.recyclerview.widget.RecyclerView
52 import androidx.viewpager2.widget.ViewPager2 52 import androidx.viewpager2.widget.ViewPager2
53 import com.google.android.material.tabs.TabLayout 53 import com.google.android.material.tabs.TabLayout
54 import com.google.android.material.tabs.TabLayout.OnTabSelectedListener 54 import com.google.android.material.tabs.TabLayout.OnTabSelectedListener
55 import com.google.android.material.tabs.TabLayoutMediator 55 import com.google.android.material.tabs.TabLayoutMediator
56 import java.io.BufferedInputStream
56 import java.io.File 57 import java.io.File
58 import java.io.FileOutputStream
57 import java.io.IOException 59 import java.io.IOException
58 import java.util.* 60 import java.util.*
59 import java.util.concurrent.locks.ReentrantLock 61 import java.util.concurrent.locks.ReentrantLock
62 import java.util.zip.ZipEntry
63 import java.util.zip.ZipFile
60 64
61 object DWEvent { 65 object DWEvent {
62 const val TIMER = 0 66 const val TIMER = 0
63 const val CONFIGURE = 1 67 const val CONFIGURE = 1
64 const val KEY_PRESS = 2 68 const val KEY_PRESS = 2
79 const val COLUMN_CLICK = 17 83 const val COLUMN_CLICK = 17
80 const val HTML_RESULT = 18 84 const val HTML_RESULT = 18
81 const val HTML_CHANGED = 19 85 const val HTML_CHANGED = 19
82 } 86 }
83 87
88 val DWImageExts = arrayOf("", ".png", ".webp", ".jpg", ".jpeg", ".gif")
89
84 class DWTabViewPagerAdapter : RecyclerView.Adapter<DWTabViewPagerAdapter.DWEventViewHolder>() { 90 class DWTabViewPagerAdapter : RecyclerView.Adapter<DWTabViewPagerAdapter.DWEventViewHolder>() {
85 val viewList = mutableListOf<LinearLayout>() 91 val viewList = mutableListOf<LinearLayout>()
86 val pageList = mutableListOf<Long>() 92 val pageList = mutableListOf<Long>()
87 var currentPageID = 0L 93 var currentPageID = 0L
88 94
794 threadCond.await() 800 threadCond.await()
795 threadLock.unlock() 801 threadLock.unlock()
796 } 802 }
797 } 803 }
798 804
805 // Returns true if the filename is a resource ID (non-zero number)
806 // with a image file extension in our DWImageExts list
807 private fun isDWResource(filename: String): Boolean {
808 val length = filename.length
809
810 for (ext in DWImageExts) {
811 if (ext.isNotEmpty() && filename.endsWith(ext)) {
812 val filebody: String = filename.substring(7, length - ext.length)
813 if (filebody.toInt() > 0) {
814 return true
815 }
816 }
817 }
818 return false
819 }
820
821 /**
822 * extracts assets/ in the APK to the application cache directory
823 */
824 private fun extractAssets() {
825 var zipFile: ZipFile? = null
826 val targetDir = cacheDir
827
828 try {
829 zipFile = ZipFile(this.applicationInfo.sourceDir)
830 val e: Enumeration<out ZipEntry?> = zipFile.entries()
831 while (e.hasMoreElements()) {
832 val entry: ZipEntry? = e.nextElement()
833 if (entry == null || entry.isDirectory || !entry.name.startsWith("assets/") ||
834 isDWResource(entry.name)) {
835 continue
836 }
837 val targetFile = File(targetDir, entry.name.substring("assets/".length))
838 targetFile.parentFile!!.mkdirs()
839 val tempBuffer = ByteArray(entry.size.toInt())
840 var ais: BufferedInputStream? = null
841 var aos: FileOutputStream? = null
842 try {
843 ais = BufferedInputStream(zipFile.getInputStream(entry))
844 aos = FileOutputStream(targetFile)
845 ais.read(tempBuffer)
846 aos.write(tempBuffer)
847 } catch (e: IOException) {
848 } finally {
849 ais?.close()
850 aos?.close()
851 }
852 }
853 } catch (e: IOException) {
854 } finally {
855 zipFile?.close()
856 }
857 }
858
799 // We only want to call this once when the app starts up 859 // We only want to call this once when the app starts up
800 // By default Android will call onCreate for rotation and other 860 // By default Android will call onCreate for rotation and other
801 // changes. This is incompatible with Dynamic Windows... 861 // changes. This is incompatible with Dynamic Windows...
802 // Make sure the following is in your AndroidManifest.xml 862 // Make sure the following is in your AndroidManifest.xml
803 // android:configChanges="orientation|screenSize|screenLayout|keyboardHidden" 863 // android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
810 // Get the Android app path 870 // Get the Android app path
811 val m = packageManager 871 val m = packageManager
812 var s = packageName 872 var s = packageName
813 val p = m.getPackageInfo(s!!, 0) 873 val p = m.getPackageInfo(s!!, 0)
814 s = p.applicationInfo.dataDir 874 s = p.applicationInfo.dataDir
875 val c = cacheDir.path
876
877 // Extract any non-resource assets to the cache directory
878 // So that our C code can access them as files, like on
879 // other Dynamic Windows platforms
880 extractAssets()
815 881
816 // Initialize the Dynamic Windows code... 882 // Initialize the Dynamic Windows code...
817 // This will start a new thread that calls the app's dwmain() 883 // This will start a new thread that calls the app's dwmain()
818 dwindowsInit(s, this.getPackageName()) 884 dwindowsInit(s, c, this.getPackageName())
819 } 885 }
820 886
821 override fun onConfigurationChanged(newConfig: Configuration) { 887 override fun onConfigurationChanged(newConfig: Configuration) {
822 super.onConfigurationChanged(newConfig) 888 super.onConfigurationChanged(newConfig)
823 889
1464 fun bitmapButtonNew(text: String, resid: Int): ImageButton? { 1530 fun bitmapButtonNew(text: String, resid: Int): ImageButton? {
1465 var button: ImageButton? = null 1531 var button: ImageButton? = null
1466 waitOnUiThread { 1532 waitOnUiThread {
1467 button = ImageButton(this) 1533 button = ImageButton(this)
1468 val dataArrayMap = SimpleArrayMap<String, Long>() 1534 val dataArrayMap = SimpleArrayMap<String, Long>()
1469 val exts = arrayOf("", ".png", ".webp", ".jpg", ".jpeg", ".gif")
1470 var filename: String? = null 1535 var filename: String? = null
1471 1536
1472 button!!.tag = dataArrayMap 1537 button!!.tag = dataArrayMap
1473 button!!.id = resid 1538 button!!.id = resid
1474 button!!.setImageResource(resid) 1539 button!!.setImageResource(resid)
1480 if(resid > 0 && resid < 65536) { 1545 if(resid > 0 && resid < 65536) {
1481 filename = resid.toString() 1546 filename = resid.toString()
1482 } 1547 }
1483 1548
1484 if(filename != null) { 1549 if(filename != null) {
1485 for (ext in exts) { 1550 for (ext in DWImageExts) {
1486 // Try to load the image, and protect against exceptions 1551 // Try to load the image, and protect against exceptions
1487 try { 1552 try {
1488 val f = this.assets.open(filename + ext) 1553 val f = this.assets.open(filename + ext)
1489 val b = BitmapFactory.decodeStream(f) 1554 val b = BitmapFactory.decodeStream(f)
1490 1555
1503 fun bitmapButtonNewFromFile(text: String, cid: Int, filename: String): ImageButton? { 1568 fun bitmapButtonNewFromFile(text: String, cid: Int, filename: String): ImageButton? {
1504 var button: ImageButton? = null 1569 var button: ImageButton? = null
1505 waitOnUiThread { 1570 waitOnUiThread {
1506 button = ImageButton(this) 1571 button = ImageButton(this)
1507 val dataArrayMap = SimpleArrayMap<String, Long>() 1572 val dataArrayMap = SimpleArrayMap<String, Long>()
1508 val exts = arrayOf("", ".png", ".webp", ".jpg", ".jpeg", ".gif")
1509 1573
1510 button!!.tag = dataArrayMap 1574 button!!.tag = dataArrayMap
1511 button!!.id = cid 1575 button!!.id = cid
1512 button!!.setOnClickListener { 1576 button!!.setOnClickListener {
1513 lastClickView = button!! 1577 lastClickView = button!!
1514 eventHandlerSimple(button!!, DWEvent.CLICKED) 1578 eventHandlerSimple(button!!, DWEvent.CLICKED)
1515 } 1579 }
1516 1580
1517 for (ext in exts) { 1581 for (ext in DWImageExts) {
1518 // Try to load the image, and protect against exceptions 1582 // Try to load the image, and protect against exceptions
1519 try { 1583 try {
1520 val f = this.assets.open(filename + ext) 1584 val f = this.assets.open(filename + ext)
1521 val b = BitmapFactory.decodeStream(f) 1585 val b = BitmapFactory.decodeStream(f)
1522 1586
2978 3042
2979 imageview.setImageResource(resID) 3043 imageview.setImageResource(resID)
2980 } 3044 }
2981 } 3045 }
2982 if(filename != null) { 3046 if(filename != null) {
2983 val exts = arrayOf("", ".png", ".webp", ".jpg", ".jpeg", ".gif") 3047 for (ext in DWImageExts) {
2984
2985 for (ext in exts) {
2986 // Try to load the image, and protect against exceptions 3048 // Try to load the image, and protect against exceptions
2987 try { 3049 try {
2988 val f = this.assets.open(filename + ext) 3050 val f = this.assets.open(filename + ext)
2989 val b = BitmapFactory.decodeStream(f) 3051 val b = BitmapFactory.decodeStream(f)
2990 3052
3060 filename = file 3122 filename = file
3061 } 3123 }
3062 // Handle filename or DW resource IDs 3124 // Handle filename or DW resource IDs
3063 // these will be located in the assets folder 3125 // these will be located in the assets folder
3064 if(filename != null) { 3126 if(filename != null) {
3065 val exts = arrayOf("", ".png", ".webp", ".jpg", ".jpeg", ".gif") 3127 for (ext in DWImageExts) {
3066
3067 for (ext in exts) {
3068 // Try to load the image, and protect against exceptions 3128 // Try to load the image, and protect against exceptions
3069 try { 3129 try {
3070 val f = this.assets.open(filename + ext) 3130 val f = this.assets.open(filename + ext)
3071 icon = Drawable.createFromStream(f, null) 3131 icon = Drawable.createFromStream(f, null)
3072 } catch (e: IOException) { 3132 } catch (e: IOException) {
3097 pixmap = BitmapFactory.decodeByteArray(data, 0, length) 3157 pixmap = BitmapFactory.decodeByteArray(data, 0, length)
3098 } else { 3158 } else {
3099 filename = file 3159 filename = file
3100 } 3160 }
3101 if(filename != null) { 3161 if(filename != null) {
3102 val exts = arrayOf("", ".png", ".webp", ".jpg", ".jpeg", ".gif") 3162 for (ext in DWImageExts) {
3103
3104 for (ext in exts) {
3105 // Try to load the image, and protect against exceptions 3163 // Try to load the image, and protect against exceptions
3106 try { 3164 try {
3107 val f = this.assets.open(filename + ext) 3165 val f = this.assets.open(filename + ext)
3108 pixmap = BitmapFactory.decodeStream(f) 3166 pixmap = BitmapFactory.decodeStream(f)
3109 } catch (e: IOException) { 3167 } catch (e: IOException) {
3982 4040
3983 /* 4041 /*
3984 * Native methods that are implemented by the 'dwindows' native library, 4042 * Native methods that are implemented by the 'dwindows' native library,
3985 * which is packaged with this application. 4043 * which is packaged with this application.
3986 */ 4044 */
3987 external fun dwindowsInit(dataDir: String, appid: String) 4045 external fun dwindowsInit(dataDir: String, cacheDir: String, appid: String)
3988 external fun eventHandler( 4046 external fun eventHandler(
3989 obj1: View?, 4047 obj1: View?,
3990 obj2: View?, 4048 obj2: View?,
3991 message: Int, 4049 message: Int,
3992 str1: String?, 4050 str1: String?,