Walter J. Scheirer

215 papers A* 11A 21B 15C 3Misc 1Journal 121Unranked 39
YearRankTypeTitle / Venue / Authors
2026 A* conf
CHI
Louisa Conwill, Megan Levis Scheirer, Walter J. Scheirer
2026 J jnl
Image Vis. Comput.
Peter Rot, Robert Jutresa, Peter Peer, Vitomir Struc, Walter J. Scheirer, Klemen Grm
2025 B conf
IJCB
Kagan Öztürk, Louisa Conwill, Jacob Gutierrez, Kevin W. Bowyer, Walter J. Scheirer
2025 J jnl
CoRR
Kagan Öztürk, Louisa Conwill, Jacob Gutierrez, Kevin W. Bowyer, Walter J. Scheirer
2025 J jnl
CoRR
Ryan Rabinowitz, Steve Cruz, Walter J. Scheirer, Terrance E. Boult
2025 J jnl
CoRR
Pedro Alarcon Granadeno, Arturo Miguel Russell Bernal, Sofia Nelson, Demetrius Hernandez, Maureen Petterson, Michael Murphy, Walter J. Scheirer, Jane Cleland-Huang
2025 J jnl
CoRR
Jane Cleland-Huang, Pedro Alarcon Granadeno, Arturo Miguel Russell Bernal, Demetrius Hernandez, Michael Murphy, Maureen Petterson, Walter J. Scheirer
2025 A* conf
CHI
Louisa Conwill, Megan K. Levis, Karla Badillo-Urquiola, Walter J. Scheirer
2025 J jnl
CoRR
Louisa Conwill, Megan K. Levis, Karla Badillo-Urquiola, Walter J. Scheirer
2025 J jnl
CoRR
Louisa Conwill, Megan Levis Scheirer, Walter J. Scheirer
2025 A* conf
IJCAI
Derek S. Prijatelj, Samuel Grieggs, Jin Huang, Dawei Du, Ameya Shringi, Christopher Funk, Adam Kaufman, Eric Robertson, Walter J. Scheirer
2025 J jnl
CoRR
Derek S. Prijatelj, Timothy J. Ireland, Walter J. Scheirer
2025 J jnl
AI Mag.
Steve Cruz, Katarina Doctor, Christopher Funk, Walter J. Scheirer
2025 A conf
WACV
Arturo Miguel Russell Bernal, Jane Cleland-Huang, Walter J. Scheirer
2025 J jnl
CoRR
Abigail Swenor, Neil Coffee, Walter J. Scheirer
2025 J jnl
CoRR
Louisa Conwill, Megan K. Levis, Karla Badillo-Urquiola, Walter J. Scheirer
2025 B conf
FG
Sophia J. Abraham, Jonathan D. Hauenstein, Walter J. Scheirer
2025 J jnl
CoRR
Sophia J. Abraham, Jonathan D. Hauenstein, Walter J. Scheirer
2025 conf
ICCVW
Steve Cruz, Justin Dulay, Walter J. Scheirer
2025 conf
CVPR Workshops
Sophia J. Abraham, Jonathan D. Hauenstein, Walter J. Scheirer
2024 J jnl
CoRR
William Theisen, Michael Yankoski, Kristina Höök, Ernesto Verdeja, Walter J. Scheirer, Tim Weninger
2024 A conf
WACV
William Theisen, Walter J. Scheirer
2024 J jnl
CoRR
Sophia J. Abraham, Jin Huang, Brandon RichardWebster, Michael Milford, Jonathan D. Hauenstein, Walter J. Scheirer
2024 conf
CHI Extended Abstracts
Louisa Conwill, Sam English Anthony, Walter J. Scheirer
2024 J jnl
J. Artif. Intell. Res.
Derek S. Prijatelj, Samuel Grieggs, Jin Huang, Dawei Du, Ameya Shringi, Christopher Funk, Adam Kaufman, Eric Robertson, Walter J. Scheirer
2024 J jnl
Proc. IEEE
Justin Dulay, Sonia Poltoratski, Till S. Hartmann, Samuel E. Anthony, Walter J. Scheirer
2024 J jnl
CoRR
William Theisen, Walter J. Scheirer
2024 A conf
WACV
Arturo Miguel Russell Bernal, Walter J. Scheirer, Jane Cleland-Huang
2024 A conf
WACV
Zachariah Carmichael, Suhas Lohit, Anoop Cherian, Michael J. Jones, Walter J. Scheirer
2024 J jnl
CoRR
Arturo Miguel Russell Bernal, Jane Cleland-Huang, Walter J. Scheirer
2024 A conf
WACV
Samuel Grieggs, C. E. M. Henderson, Sebastian Sobecki, Alexandra Gillespie, Walter J. Scheirer
2024 conf
ECCV (37)
Zachariah Carmichael, Timothy Redgrave, Daniel Gonzalez Cedre, Walter J. Scheirer
2024 J jnl
CoRR
Zachariah Carmichael, Timothy Redgrave, Daniel Gonzalez Cedre, Walter J. Scheirer
2023 B conf
FG
Sandipan Banerjee, Walter J. Scheirer, Kevin W. Bowyer, Patrick J. Flynn
2023 J jnl
Digit. Humanit. Q.
Samuel Grieggs, Jessica Lockhart, Alexandra Atiya, Gelila Tilahun, Suzanne Akbari, Eyob Derillo, Jarod Jacobs, Christine Kwon, Michael Gervers, Steve Delamarter, Alexandra Gillespie, Walter J. Scheirer
2023 J jnl
CoRR
William Theisen, Walter J. Scheirer
2023 J jnl
CoRR
Louisa Conwill, Samuel E. Anthony, Walter J. Scheirer
2023 J jnl
CoRR
Sophia J. Abraham, Kehelwala D. G. Maduranga, Jeffery Kinnison, Zachariah Carmichael, Jonathan D. Hauenstein, Walter J. Scheirer
2023 J jnl
CoRR
Zachariah Carmichael, Walter J. Scheirer
2023 J jnl
IEEE Trans. Pattern Anal. Mach. Intell.
Jin Huang, Derek S. Prijatelj, Justin Dulay, Walter J. Scheirer
2023 A conf
WACV
William Theisen, Daniel Gonzalez Cedre, Zachariah Carmichael, Daniel Moreira, Tim Weninger, Walter J. Scheirer
2023 conf
ICCV (Workshops)
Sophia J. Abraham, Kehelwala Dewage Gayan Maduranga, Jeffery Kinnison, Jonathan D. Hauenstein, Walter J. Scheirer
2023 J jnl
CoRR
Arturo Miguel Russell Bernal, Walter J. Scheirer, Jane Cleland-Huang
2023 J jnl
CoRR
Rosaura G. VidalMata, Priscila Saboia, Daniel Moreira, Grant Jensen, Jason Schlessman, Walter J. Scheirer
2023 C conf
IH&MMSec
Walter J. Scheirer
2023 J jnl
CoRR
Zachariah Carmichael, Suhas Lohit, Anoop Cherian, Michael J. Jones, Walter J. Scheirer
2023 B conf
CogSci
Brandon RichardWebster, Justin Dulay, Anthony DiFalco, Walter J. Scheirer
2023 A* conf
AAAI
Zachariah Carmichael, Walter J. Scheirer
2022 J jnl
Pattern Recognit.
Derek S. Prijatelj, Mel McCurrie, Samuel E. Anthony, Walter J. Scheirer
2022 J jnl
CoRR
Sandipan Banerjee, Walter J. Scheirer, Kevin W. Bowyer, Patrick J. Flynn
2022 J jnl
IEEE Access
Sara Mandelli, Davide Cozzolino, Edoardo Daniele Cannas, João P. Cardenuto, Daniel Moreira, Paolo Bestagini, Walter J. Scheirer, Anderson Rocha, Luisa Verdoliva, Stefano Tubaro, Edward J. Delp
2022 J jnl
CoRR
Justin Dulay, Sonia Poltoratski, Till S. Hartmann, Samuel E. Anthony, Walter J. Scheirer
2022 J jnl
CoRR
Derek S. Prijatelj, Samuel Grieggs, Jin Huang, Dawei Du, Ameya Shringi, Christopher Funk, Adam Kaufman, Eric Robertson, Walter J. Scheirer
2022 J jnl
IEEE Trans. Pattern Anal. Mach. Intell.
Samuel Grieggs, Bingyu Shen, Greta Rauch, Pei Li, Jiaqi Ma, David Chiang, Brian L. Price, Walter J. Scheirer
2022 J jnl
CoRR
Jin Huang, Derek S. Prijatelj, Justin Dulay, Walter J. Scheirer
2022 J jnl
CoRR
William Theisen, Daniel Gonzalez Cedre, Zachariah Carmichael, Daniel Moreira, Tim Weninger, Walter J. Scheirer
2022 J jnl
CoRR
Brandon Richard Webster, Anthony DiFalco, Elisabetta Caldesi, Walter J. Scheirer
2022 J jnl
Digit. Humanit. Q.
Nozomu Okuda, Jeffery Kinnison, Patrick J. Burns, Neil Coffee, Walter J. Scheirer
2022 J jnl
CoRR
Zachariah Carmichael, Walter J. Scheirer
2022 J jnl
CoRR
Justin Dulay, Walter J. Scheirer
2021 B conf
FG
Bingyu Shen, Brandon RichardWebster, Alice J. O'Toole, Kevin W. Bowyer, Walter J. Scheirer
2021 J jnl
CoRR
Bingyu Shen, Brandon RichardWebster, Alice J. O'Toole, Kevin W. Bowyer, Walter J. Scheirer
2021 conf
WAIN@ICSE
Sophia J. Abraham, Zachariah Carmichael, Sreya Banerjee, Rosaura G. VidalMata, Ankit Agrawal, Md Nafee Al Islam, Walter J. Scheirer, Jane Cleland-Huang
2021 J jnl
CoRR
Sophia J. Abraham, Zachariah Carmichael, Sreya Banerjee, Rosaura G. VidalMata, Ankit Agrawal, Md Nafee Al Islam, Walter J. Scheirer, Jane Cleland-Huang
2021 A conf
ICWSM
William Theisen, Joel Brogan, Pamela Bilo Thomas, Daniel Moreira, Pascal Phoa, Tim Weninger, Walter J. Scheirer
2021 conf
WACV (Workshops)
Bingyu Shen, Boyang Li, Walter J. Scheirer
2021 J jnl
IEEE Trans. Pattern Anal. Mach. Intell.
Rosaura G. VidalMata, Sreya Banerjee, Brandon RichardWebster, Michael Albright, Pedro Davalos, Scott McCloskey, Ben Miller, Asong Tambo, Sushobhan Ghosh, Sudarshan Nagesh, Ye Yuan, Yueyu Hu, Junru Wu, Wenhan Yang, Xiaoshuai Zhang, Jiaying Liu, Zhangyang Wang, Hwann-Tzong Chen, Tzu-Wei Huang, Wen-Chi Chin, Yi-Chun Li, Mahmoud Lababidi, Charles Otto, Walter J. Scheirer
2021 J jnl
IEEE Trans. Image Process.
Joel Brogan, Aparna Bharati, Daniel Moreira, Anderson Rocha, Kevin W. Bowyer, Patrick J. Flynn, Walter J. Scheirer
2021 J jnl
CoRR
Sara Mandelli, Davide Cozzolino, João P. Cardenuto, Daniel Moreira, Paolo Bestagini, Walter J. Scheirer, Anderson Rocha, Luisa Verdoliva, Stefano Tubaro, Edward J. Delp
2021 conf
ICDAR (4)
Derek S. Prijatelj, Samuel Grieggs, Futoshi Yumoto, Eric Robertson, Walter J. Scheirer
2021 J jnl
CoRR
Derek S. Prijatelj, Samuel Grieggs, Futoshi Yumoto, Eric Robertson, Walter J. Scheirer
2021 A conf
WACV
Rosaura G. VidalMata, Walter J. Scheirer, Anna Kukleva, David D. Cox, Hilde Kuehne
2021 J jnl
CoRR
Zachariah Carmichael, Walter J. Scheirer
2021 J jnl
IEEE Trans. Inf. Forensics Secur.
Blaz Meden, Peter Rot, Philipp Terhörst, Naser Damer, Arjan Kuijper, Walter J. Scheirer, Arun Ross, Peter Peer, Vitomir Struc
2021 J jnl
Comput. Vis. Image Underst.
Sreya Banerjee, Rosaura G. VidalMata, Zhangyang Wang, Walter J. Scheirer
2021 A* conf
AAAI
Terrance E. Boult, Przemyslaw A. Grabowicz, Derek S. Prijatelj, Roni Stern, Lawrence Holder, Joshua Alspector, Mohsen Jafarzadeh, Touqeer Ahmad, Akshay Raj Dhamija, Chunchun Li, Steve Cruz, Abhinav Shrivastava, Carl Vondrick, Walter J. Scheirer
2021 J jnl
IEEE Trans. Inf. Forensics Secur.
Aparna Bharati, Daniel Moreira, Patrick J. Flynn, Anderson de Rezende Rocha, Kevin W. Bowyer, Walter J. Scheirer
2020 J jnl
CoRR
Derek S. Prijatelj, Mel McCurrie, Walter J. Scheirer
2020 J jnl
CoRR
Terrance E. Boult, Przemyslaw A. Grabowicz, Derek S. Prijatelj, Roni Stern, Larry B. Holder, Joshua Alspector, Mohsen Jafarzadeh, Touqeer Ahmad, Akshay Raj Dhamija, Chunchun Li, Steve Cruz, Abhinav Shrivastava, Carl Vondrick, Walter J. Scheirer
2020 J jnl
IEEE Trans. Image Process.
Wenhan Yang, Ye Yuan, Wenqi Ren, Jiaying Liu, Walter J. Scheirer, Zhangyang Wang, Taiheng Zhang, Qiaoyong Zhong, Di Xie, Shiliang Pu, Yuqiang Zheng, Yanyun Qu, Yuhong Xie, Liang Chen, Zhonghao Li, Chen Hong, Hao Jiang, Siyuan Yang, Yan Liu, Xiaochao Qu, Pengfei Wan, Shuai Zheng, Minhui Zhong, Taiyi Su, Lingzhi He, Yandong Guo, Yao Zhao, Zhenfeng Zhu, Jinxiu Liang, Jingwen Wang, Tianyi Chen, Yuhui Quan, Yong Xu, Bo Liu, Xin Liu, Qi Sun, Tingyu Lin, Xiaochuan Li, Feng Lu, Lin Gu, Shengdi Zhou, Cong Cao, Shifeng Zhang, Cheng Chi, Chubin Zhuang, Zhen Lei, Stan Z. Li, Shizheng Wang, Ruizhe Liu, Dong Yi, Zheming Zuo, Jianning Chi, Huan Wang, Kai Wang, Yixiu Liu, Xingyu Gao, Zhenyu Chen, Chang Guo, Yongzhou Li, Huicai Zhong, Jing Huang, Heng Guo, Jianfei Yang, Wenjuan Liao, Jiangang Yang, Liguo Zhou, Mingyue Feng, Likun Qin
2020 J jnl
CoRR
William Theisen, Joel Brogan, Pamela Bilo Thomas, Daniel Moreira, Pascal Phoa, Tim Weninger, Walter J. Scheirer
2020 B conf
IJCB
Jacob Dumford, Walter J. Scheirer
2020 J jnl
IEEE Trans. Image Process.
Klemen Grm, Walter J. Scheirer, Vitomir Struc
2020 conf
DH
Nozomu Okuda, Jeffery Kinnison, Neil Coffee, Walter J. Scheirer
2020 J jnl
CoRR
Rosaura G. VidalMata, Walter J. Scheirer, Hilde Kuehne
2020 J jnl
CoRR
Aparna Bharati, Daniel Moreira, Patrick J. Flynn, Anderson Rocha, Kevin W. Bowyer, Walter J. Scheirer
2020 B conf
IJCB
Mel McCurrie, Hamish Nicholson, Walter J. Scheirer, Samuel E. Anthony
2020 J jnl
CoRR
Mel McCurrie, Hamish Nicholson, Walter J. Scheirer, Samuel E. Anthony
2020 A conf
WACV
Sandipan Banerjee, Walter J. Scheirer, Kevin W. Bowyer, Patrick J. Flynn
2020 conf
ICBINB@NeurIPS
Stella Biderman, Walter J. Scheirer
2020 J jnl
CoRR
Stella Biderman, Walter J. Scheirer
2020 conf
DH
Walter J. Scheirer, Christopher W. Forstall
2020 J jnl
CoRR
Kevin W. Bowyer, Michael C. King, Walter J. Scheirer
2020 A* conf
CHI
Ankit Agrawal, Sophia J. Abraham, Benjamin Burger, Chichi Christine, Luke Fraser, John M. Hoeksema, Sarah Hwang, Elizabeth Travnik, Shreya Kumar, Walter J. Scheirer, Jane Cleland-Huang, Michael Vierhauser, Ryan Bauer, Steve Cox
2020 J jnl
CoRR
Ankit Agrawal, Sophia J. Abraham, Benjamin Burger, Chichi Christine, Luke Fraser, John M. Hoeksema, Sara Hwang, Elizabeth Travnik, Shreya Kumar, Walter J. Scheirer, Jane Cleland-Huang, Michael Vierhauser, Ryan Bauer, Steve Cox
2019 A conf
WACV
Nathaniel Blanchard, Kyle Skinner, Aden Kemp, Walter J. Scheirer, Patrick J. Flynn
2019 A* conf
CVPR
Nathaniel Blanchard, Jeffery Kinnison, Brandon RichardWebster, Pouya Bashivan, Walter J. Scheirer
2019 J jnl
Frontiers Comput. Neurosci.
Sreya Banerjee, Walter J. Scheirer, Lei Li
2019 conf
NGT@EMNLP-IJCNLP
Kenton Murray, Jeffery Kinnison, Toan Q. Nguyen, Walter J. Scheirer, David Chiang
2019 J jnl
CoRR
Kenton Murray, Jeffery Kinnison, Toan Q. Nguyen, Walter J. Scheirer, David Chiang
2019 A conf
WACV
Aparna Bharati, Daniel Moreira, Joel Brogan, Patricia Hale, Kevin W. Bowyer, Patrick J. Flynn, Anderson Rocha, Walter J. Scheirer
2019 J jnl
CoRR
Rosaura G. VidalMata, Sreya Banerjee, Brandon RichardWebster, Michael Albright, Pedro Davalos, Scott McCloskey, Ben Miller, Asong Tambo, Sushobhan Ghosh, Sudarshan Nagesh, Ye Yuan, Yueyu Hu, Junru Wu, Wenhan Yang, Xiaoshuai Zhang, Jiaying Liu, Zhangyang Wang, Hwann-Tzong Chen, Tzu-Wei Huang, Wen-Chi Chin, Yi-Chun Li, Mahmoud Lababidi, Charles Otto, Walter J. Scheirer
2019 conf
CVPR Workshops
Klemen Grm, Martin Pernus, Leo Cluzel, Walter J. Scheirer, Simon Dobrisek, Vitomir Struc
2019 A conf
WACV
Sandipan Banerjee, Walter J. Scheirer, Kevin W. Bowyer, Patrick J. Flynn
2019 conf
ICB
Kálmán Tornai, Walter J. Scheirer
2019 A* conf
AAAI
Terrance E. Boult, Steve Cruz, Akshay Raj Dhamija, Manuel Günther, James Henrydoss, Walter J. Scheirer
2019 conf
ICB
Jeffery Kinnison, Mateusz Trokielewicz, Camila Carballo, Adam Czajka, Walter J. Scheirer
2019 J jnl
CoRR
Jeffery Kinnison, Mateusz Trokielewicz, Camila Carballo, Adam Czajka, Walter J. Scheirer
2019 J jnl
CoRR
Samuel Grieggs, Bingyu Shen, Pei Li, Cana Short, Jiaqi Ma, Mihow McKenny, Melody Wauke, Brian L. Price, Walter J. Scheirer
2019 J jnl
CoRR
Joel Brogan, Aparna Bharati, Daniel Moreira, Kevin W. Bowyer, Patrick J. Flynn, Anderson Rocha, Walter J. Scheirer
2019 J jnl
IEEE Trans. Pattern Anal. Mach. Intell.
Brandon RichardWebster, Samuel E. Anthony, Walter J. Scheirer
2019 book
Christopher W. Forstall, Walter J. Scheirer
2019 J jnl
CoRR
Sreya Banerjee, Rosaura G. VidalMata, Zhangyang Wang, Walter J. Scheirer
2019 J jnl
CoRR
Ye Yuan, Wenhan Yang, Wenqi Ren, Jiaying Liu, Walter J. Scheirer, Zhangyang Wang
2018 J jnl
CoRR
Nathaniel Blanchard, Jeffery Kinnison, Brandon RichardWebster, Pouya Bashivan, Walter J. Scheirer
2018 J jnl
CoRR
Jacob Dumford, Walter J. Scheirer
2018 J jnl
CoRR
Aparna Bharati, Daniel Moreira, Joel Brogan, Patricia Hale, Kevin W. Bowyer, Patrick J. Flynn, Anderson Rocha, Walter J. Scheirer
2018 J jnl
Image Vis. Comput.
Mel McCurrie, Fernando Beletti, Lucas Parzianello, Allen Westendorp, Samuel E. Anthony, Walter J. Scheirer
2018 A conf
IUI
Ronald A. Metoyer, Qiyu Zhi, Bart Janczuk, Walter J. Scheirer
2018 J jnl
CoRR
Klemen Grm, Martin Pernus, Leo Cluzel, Walter J. Scheirer, Simon Dobrisek, Vitomir Struc
2018 J jnl
CoRR
Klemen Grm, Simon Dobrisek, Walter J. Scheirer, Vitomir Struc
2018 J jnl
CoRR
Sandipan Banerjee, Walter J. Scheirer, Kevin W. Bowyer, Patrick J. Flynn
2018 J jnl
CoRR
Nathaniel Blanchard, Daniel Moreira, Aparna Bharati, Walter J. Scheirer
2018 J jnl
CoRR
Daniel Moreira, Aparna Bharati, Joel Brogan, Allan da Silva Pinto, Michael Parowski, Kevin W. Bowyer, Patrick J. Flynn, Anderson Rocha, Walter J. Scheirer
2018 J jnl
IEEE Trans. Image Process.
Aparna Bharati, Joel Brogan, Allan da Silva Pinto, Michael Parowski, Kevin W. Bowyer, Patrick J. Flynn, Anderson Rocha, Walter J. Scheirer
2018 J jnl
CoRR
Sandipan Banerjee, Walter J. Scheirer, Kevin W. Bowyer, Patrick J. Flynn
2018 J jnl
IEEE Access
Bingyu Shen, Christopher W. Forstall, Anderson de Rezende Rocha, Walter J. Scheirer
2018 A conf
WACV
Jeffery Kinnison, Nathaniel Kremer-Herman, Douglas Thain, Walter J. Scheirer
2018 J jnl
IEEE Trans. Pattern Anal. Mach. Intell.
Ethan M. Rudd, Lalit P. Jain, Walter J. Scheirer, Terrance E. Boult
2018 J jnl
CoRR
Niko Sünderhauf, Oliver Brock, Walter J. Scheirer, Raia Hadsell, Dieter Fox, Jürgen Leitner, Ben Upcroft, Pieter Abbeel, Wolfram Burgard, Michael Milford, Peter Corke
2018 J jnl
Int. J. Robotics Res.
Niko Sünderhauf, Oliver Brock, Walter J. Scheirer, Raia Hadsell, Dieter Fox, Jürgen Leitner, Ben Upcroft, Pieter Abbeel, Wolfram Burgard, Michael Milford, Peter Corke
2018 A conf
WACV
Sandipan Banerjee, Joel Brogan, Janez Krizaj, Aparna Bharati, Brandon RichardWebster, Vitomir Struc, Patrick J. Flynn, Walter J. Scheirer
2018 J jnl
IEEE Intell. Syst.
Hugo Proença, Mark S. Nixon, Michele Nappi, Esam Ghaleb, Gökhan Özbulak, Hua Gao, Hazim Kemal Ekenel, Klemen Grm, Vitomir Struc, Hailin Shi, Xiangyu Zhu, Shengcai Liao, Zhen Lei, Stan Z. Li, Weronika Gutfeter, Andrzej Pacut, Joel Brogan, Walter J. Scheirer, Ester Gonzalez-Sosa, Rubén Vera-Rodríguez, Julian Fiérrez, Javier Ortega-Garcia, Daniel Riccio, Luigi De Maio
2018 A conf
WACV
Rosaura G. Vidal, Sreya Banerjee, Klemen Grm, Vitomir Struc, Walter J. Scheirer
2018 conf
DH
Samuel Grieggs, Bingyu Shen, Hildegund Muller, Christine Ascik, Erik Ellis, Mihow McKenny, Nikolas Churik, Emily Mahan, Walter J. Scheirer
2018 conf
ECCV (15)
Brandon RichardWebster, So Yon Kwon, Christopher Clarizio, Samuel E. Anthony, Walter J. Scheirer
2018 J jnl
CoRR
Brandon RichardWebster, So Yon Kwon, Christopher Clarizio, Samuel E. Anthony, Walter J. Scheirer
2018 conf
ECCV Workshops (6)
Alexander Freytag, Vittorio Ferrari, Mario Fritz, Uwe Franke, Terrance E. Boult, Juergen Gall, Walter J. Scheirer, Angela Yao, Erik Rodner
2017 J jnl
IEEE Trans. Inf. Forensics Secur.
Anderson Rocha, Walter J. Scheirer, Christopher W. Forstall, Thiago Cavalcante, Antonio Theophilo, Bingyu Shen, Ariadne Carvalho, Efstathios Stamatatos
2017 J jnl
CoRR
Ronald A. Metoyer, Walter J. Scheirer, Qiyu Zhi, Bart Janczuk
2017 J jnl
CoRR
Adam Jacobson, Walter J. Scheirer, Michael Milford
2017 A conf
IROS
Adam Jacobson, Walter J. Scheirer, Michael Milford
2017 book
Walter J. Scheirer
2017 conf
MICCAI (2)
Jianxu Chen, Sreya Banerjee, Abhinav Grama, Walter J. Scheirer, Danny Z. Chen
2017 J jnl
CoRR
Jianxu Chen, Sreya Banerjee, Abhinav Grama, Walter J. Scheirer, Danny Z. Chen
2017 B conf
FG
Mel McCurrie, Samuel E. Anthony, Walter J. Scheirer
2017 B conf
FG
Mel McCurrie, Fernando Beletti, Lucas Parzianello, Allen Westendorp, Samuel E. Anthony, Walter J. Scheirer
2017 J jnl
CoRR
Allan da Silva Pinto, Daniel Moreira, Aparna Bharati, Joel Brogan, Kevin W. Bowyer, Patrick J. Flynn, Walter J. Scheirer, Anderson Rocha
2017 B conf
ICIP
Allan da Silva Pinto, Daniel Moreira, Aparna Bharati, Joel Brogan, Kevin W. Bowyer, Patrick J. Flynn, Walter J. Scheirer, Anderson Rocha
2017 J jnl
CoRR
Jeff Kinnison, Nathaniel Kremer-Herman, Douglas Thain, Walter J. Scheirer
2017 J jnl
CoRR
Sandipan Banerjee, John S. Bernhard, Walter J. Scheirer, Kevin W. Bowyer, Patrick J. Flynn
2017 B conf
IJCB
Sandipan Banerjee, John S. Bernhard, Walter J. Scheirer, Kevin W. Bowyer, Patrick J. Flynn
2017 J jnl
CoRR
Joel Brogan, Paolo Bestagini, Aparna Bharati, Allan da Silva Pinto, Daniel Moreira, Kevin W. Bowyer, Patrick J. Flynn, Anderson Rocha, Walter J. Scheirer
2017 B conf
ICIP
Joel Brogan, Paolo Bestagini, Aparna Bharati, Allan da Silva Pinto, Daniel Moreira, Kevin W. Bowyer, Patrick J. Flynn, Anderson Rocha, Walter J. Scheirer
2017 J jnl
CoRR
Aparna Bharati, Daniel Moreira, Allan da Silva Pinto, Joel Brogan, Kevin W. Bowyer, Patrick J. Flynn, Walter J. Scheirer, Anderson Rocha
2017 B conf
ICIP
Aparna Bharati, Daniel Moreira, Allan da Silva Pinto, Joel Brogan, Kevin W. Bowyer, Patrick J. Flynn, Walter J. Scheirer, Anderson Rocha
2017 J jnl
CoRR
Rosaura G. Vidal, Sreya Banerjee, Klemen Grm, Vitomir Struc, Walter J. Scheirer
2017 J jnl
CoRR
Ruth Fong, Walter J. Scheirer, David D. Cox
2016 J jnl
Appl. Soft Comput.
João Paulo Papa, Walter J. Scheirer, David Daniel Cox
2016 J jnl
CoRR
Victor Fragoso, Walter J. Scheirer, João Pedro Hespanha, Matthew Turk
2016 B conf
ICPR
Victor Fragoso, Walter J. Scheirer, João Pedro Hespanha, Matthew Turk
2016 J jnl
CoRR
Mel McCurrie, Fernando Beletti, Lucas Parzianello, Allen Westendorp, Samuel E. Anthony, Walter J. Scheirer
2016 J jnl
CoRR
Brandon RichardWebster, Samuel E. Anthony, Walter J. Scheirer
2016 conf
BTAS
Walter J. Scheirer, Patrick J. Flynn, Changxing Ding, Guodong Guo, Vitomir Struc, Mohamad Al Jazaery, Klemen Grm, Simon Dobrisek, Dacheng Tao, Yu Zhu, Joel Brogan, Sandipan Banerjee, Aparna Bharati, Brandon RichardWebster
2016 J jnl
Digit. Scholarsh. Humanit.
Walter J. Scheirer, Christopher W. Forstall, Neil Coffee
2016 J jnl
CoRR
Sandipan Banerjee, Joel Brogan, Janez Krizaj, Aparna Bharati, Brandon RichardWebster, Vitomir Struc, Patrick J. Flynn, Walter J. Scheirer
2015 C conf
CIARP
Gustavo H. Rosa, João Paulo Papa, Aparecido Nilceu Marana, Walter J. Scheirer, David D. Cox
2015 C conf
CIARP
Silas Evandro Nachif Fernandes, Walter J. Scheirer, David D. Cox, João Paulo Papa
2015 J jnl
EURASIP J. Image Video Process.
Anderson Rocha, Walter J. Scheirer
2015 J jnl
J. Comput. Sci.
João Paulo Papa, Gustavo H. Rosa, Aparecido Nilceu Marana, Walter J. Scheirer, David D. Cox
2015 conf
GECCO (Companion)
João P. Papa, Gustavo Henrique Rosa, Kelton A. P. Costa, Nilceu A. Marana, Walter J. Scheirer, David Daniel Cox
2015 J jnl
IEEE Trans. Inf. Forensics Secur.
Ajita Rattani, Walter J. Scheirer, Arun Ross
2015 J jnl
CoRR
Ethan M. Rudd, Lalit P. Jain, Walter J. Scheirer, Terrance E. Boult
2014 A* conf
ICRA
Michael Milford, Walter J. Scheirer, Eleonora Vig, Arren Glover, Oliver Baumann, Jason B. Mattingley, David D. Cox
2014 conf
DH
Walter J. Scheirer, Christopher W. Forstall
2014 J jnl
Pattern Recognit.
Walter J. Scheirer, Michael J. Wilber, Michael Eckmann, Terrance E. Boult
2014 conf
ECCV (3)
Lalit P. Jain, Walter J. Scheirer, Terrance E. Boult
2014 J jnl
Pattern Recognit. Lett.
Filipe de Oliveira Costa, Ewerton Silva, Michael Eckmann, Walter J. Scheirer, Anderson Rocha
2014 J jnl
IEEE Trans. Pattern Anal. Mach. Intell.
Walter J. Scheirer, Samuel E. Anthony, Ken Nakayama, David D. Cox
2014 J jnl
IEEE Trans. Pattern Anal. Mach. Intell.
Walter J. Scheirer, Lalit P. Jain, Terrance E. Boult
2014 J jnl
J. Field Robotics
Michael Milford, Eleonora Vig, Walter J. Scheirer, David D. Cox
2013 A conf
WACV
Michael J. Wilber, Walter J. Scheirer, Phil Leitner, Brian Heflin, James Zott, Daniel Reinke, David K. Delaney, Terrance E. Boult
2013 ch.
Security and Privacy in Biometrics
Walter J. Scheirer, William Bishop, Terrance E. Boult
2013 J jnl
CoRR
Walter J. Scheirer, Michael J. Wilber, Michael Eckmann, Terrance E. Boult
2013 conf
DH
Neil Coffee, James O. Gawley, Christopher W. Forstall, Walter J. Scheirer, David M. Johnson, Jason J. Corso, Brian C. Parks
2013 J jnl
IEEE Trans. Pattern Anal. Mach. Intell.
Walter J. Scheirer, Anderson de Rezende Rocha, Archana Sapkota, Terrance E. Boult
2013 conf
BTAS
RC Johnson, Terrance E. Boult, Walter J. Scheirer
2012 conf
BTAS
Brian Heflin, Walter J. Scheirer, Terrance E. Boult
2012 A conf
WACV
Brian Heflin, Walter J. Scheirer, Terrance E. Boult
2012 J jnl
IEEE Trans. Inf. Forensics Secur.
Walter J. Scheirer, Anderson de Rezende Rocha, Jonathan Parris, Terrance E. Boult
2012 A* conf
CVPR
Walter J. Scheirer, Neeraj Kumar, Peter N. Belhumeur, Terrance E. Boult
2012 conf
SIBGRAPI
Filipe de Oliveira Costa, Michael Eckmann, Walter J. Scheirer, Anderson Rocha
2012 conf
CVPR Workshops
Michael J. Wilber, Walter J. Scheirer, Terrance E. Boult
2012 J jnl
CoRR
RC Johnson, Walter J. Scheirer, Terrance E. Boult
2011 J jnl
Lit. Linguistic Comput.
Christopher W. Forstall, Sarah L. Jacobson, Walter J. Scheirer
2011 B conf
IJCB
Walter J. Scheirer, Neeraj Kumar, Karl Ricanek, Peter N. Belhumeur, Terrance E. Boult
2011 J jnl
IEEE Trans. Pattern Anal. Mach. Intell.
Walter J. Scheirer, Anderson Rocha, Ross J. Micheals, Terrance E. Boult
2011 J jnl
ACM Comput. Surv.
Anderson Rocha, Walter J. Scheirer, Terrance E. Boult, Siome Goldenstein
2011 conf
DH
Christopher W. Forstall, Walter J. Scheirer
2010 conf
CVPR Workshops
V. N. Iyer, S. R. Kirkbride, Brian C. Parks, Walter J. Scheirer, Terrance E. Boult
2010 conf
WIFS
Walter J. Scheirer, William Bishop, Terrance E. Boult
2010 conf
BTAS
Brian Heflin, Walter J. Scheirer, Terrance E. Boult
2010 conf
DH
Christopher W. Forstall, Sarah L. Jacobson, Walter J. Scheirer
2010 conf
CVPR Workshops
Archana Sapkota, Brian C. Parks, Walter J. Scheirer, Terrance E. Boult
2010 conf
BTAS
Vijay Iyer, Walter J. Scheirer, Terrance E. Boult
2010 conf
ECCV (3)
Walter J. Scheirer, Anderson Rocha, Ross J. Micheals, Terrance E. Boult
2009 conf
ICB
Walter J. Scheirer, Terrance E. Boult
2009 ch.
Handbook of Remote Biometrics
Terrance E. Boult, Walter J. Scheirer
2008 A conf
WACV
Walter J. Scheirer, S. R. Kirkbride, Terrance E. Boult
2008 conf
CVPR Workshops
Walter J. Scheirer, Abhijit Bendale, Terrance E. Boult
2008 J jnl
Int. J. Secur. Networks
Walter J. Scheirer, Mooi Choo Chuah
2008 conf
CVPR Workshops
Anderson Rocha, Siome Goldenstein, Walter J. Scheirer, Terrance E. Boult
2007 A* conf
CVPR
Terrance E. Boult, Walter J. Scheirer, Robert Woodworth
2006 A conf
IPDPS
Walter J. Scheirer, Mooi Choo Chuah
2006 Misc conf
CISS
Walter J. Scheirer, Mooi Choo Chuah
tests/unit/test_apk_extractors.py
← Index tests/unit/test_apk_extractors.py python
"""
Unit tests for APK extractors.

Tests tag(), get_clickhouse_table(), extract(), and prepare_export_data()
for all 8 APK extractors. Uses mocked androguard APK objects.
"""
import pytest
import os
import hashlib
import tempfile
import zipfile
from dataclasses import asdict
from unittest.mock import MagicMock, patch, PropertyMock

pytestmark = [pytest.mark.unit, pytest.mark.apk]


# ============================================================================
# Helpers
# ============================================================================

def _create_fake_apk(tmp_path, files=None):
    """Create a minimal valid ZIP file that pretends to be an APK.

    Args:
        tmp_path: Directory to write the fake APK into.
        files: Dict of {filename: content_bytes} to include in the ZIP.

    Returns:
        Path to the fake APK file.
    """
    apk_path = os.path.join(tmp_path, "test.apk")
    default_files = {
        "AndroidManifest.xml": b"<manifest/>",
        "classes.dex": b"dex\n035\x00" + b"\x00" * 100,
        "res/drawable/icon.png": b"\x89PNG" + b"\x00" * 50,
    }
    if files:
        default_files.update(files)

    with zipfile.ZipFile(apk_path, "w") as zf:
        for name, data in default_files.items():
            zf.writestr(name, data)
    return apk_path


def _make_mock_apk():
    """Create a mock androguard APK object with common return values."""
    mock_apk = MagicMock()
    mock_apk.get_package.return_value = "com.example.test"
    mock_apk.get_app_name.return_value = "Test App"
    mock_apk.get_androidversion_code.return_value = "10"
    mock_apk.get_androidversion_name.return_value = "1.2.3"
    mock_apk.get_min_sdk_version.return_value = "21"
    mock_apk.get_target_sdk_version.return_value = "33"
    mock_apk.get_effective_target_sdk_version.return_value = "33"
    mock_apk.get_main_activity.return_value = "com.example.test.MainActivity"
    mock_apk.get_attribute_value.return_value = None
    mock_apk.get_libraries.return_value = []
    mock_apk.get_files.return_value = [
        "AndroidManifest.xml", "classes.dex",
        "res/drawable/icon.png", "lib/armeabi-v7a/libnative.so",
        "assets/data.bin",
    ]
    mock_apk.get_permissions.return_value = [
        "android.permission.INTERNET",
        "android.permission.CAMERA",
    ]
    mock_apk.get_declared_permissions.return_value = {}
    mock_apk.get_activities.return_value = ["com.example.test.MainActivity"]
    mock_apk.get_services.return_value = ["com.example.test.MyService"]
    mock_apk.get_receivers.return_value = ["com.example.test.BootReceiver"]
    mock_apk.get_providers.return_value = []
    mock_apk.get_uses_implied_permission_list.return_value = []
    mock_apk.is_valid_APK.return_value = True
    mock_apk.get_certificates.return_value = []
    mock_apk.get_certificates_v1.return_value = []
    mock_apk.get_certificates_v2.return_value = []
    mock_apk.get_certificates_v3.return_value = []
    mock_apk.get_signature_names.return_value = []
    mock_apk.is_signed_v1.return_value = True
    mock_apk.is_signed_v2.return_value = True
    mock_apk.is_signed_v3.return_value = False
    return mock_apk


@pytest.fixture
def fake_apk_env(tmp_path):
    """Create a fake APK file and mock APK object."""
    apk_path = _create_fake_apk(str(tmp_path))
    mock_apk = _make_mock_apk()
    mock_logger = MagicMock()
    mock_exporters = [MagicMock()]
    return apk_path, mock_apk, mock_logger, mock_exporters


# ============================================================================
# APKFeaturesExtractor
# ============================================================================

class TestAPKFeaturesExtractor:

    def test_tag(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_features import APKFeaturesExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKFeaturesExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        assert ext.tag() == "apk_features"

    def test_clickhouse_table(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_features import APKFeaturesExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKFeaturesExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        assert ext.get_clickhouse_table() == "redb_apk_features"

    def test_extract_returns_dataclass(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_features import APKFeaturesExtractor
        from redb.models.dataclasses import APKFeatures

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKFeaturesExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        result = ext.extract()

        assert isinstance(result, APKFeatures)
        assert result.package_name == "com.example.test"
        assert result.app_name == "Test App"
        assert result.version_code == 10
        assert result.version_name == "1.2.3"
        assert result.min_sdk_version == 21
        assert result.target_sdk_version == 33
        assert result.main_activity == "com.example.test.MainActivity"

    def test_extract_detects_native_code(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_features import APKFeaturesExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKFeaturesExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        result = ext.extract()
        assert result.has_native_code is True
        assert result.has_assets is True

    def test_extract_invalid_apk_returns_none(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_features import APKFeaturesExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        mock_apk.is_valid_APK.return_value = False
        # APKExtractor sets self.apk to the mock_apk directly; _is_valid_apk checks self.apk is not None
        # For invalid APK, set apk to None
        ext = APKFeaturesExtractor(apk_path, logger, exporters=exporters, apk=None)
        # Override _is_valid_apk to simulate parsing failure
        ext.apk = None
        result = ext.extract()
        assert result is None

    def test_extract_handles_missing_version_code(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_features import APKFeaturesExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        mock_apk.get_androidversion_code.return_value = None
        ext = APKFeaturesExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        result = ext.extract()
        assert result.version_code == 0

    def test_prepare_export_data_clickhouse(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_features import APKFeaturesExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKFeaturesExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        ext.extract()
        result = ext.prepare_export_data("ClickHouseExporter")
        assert result is not None
        data, column_names, column_type_names = result
        assert "sha256" in column_names
        assert "package_name" in column_names
        assert "analysis_date" in column_names
        assert len(column_names) == len(column_type_names)
        assert len(data) == 1  # One row
        assert len(data[0]) == len(column_names)

    def test_prepare_export_data_no_extract(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_features import APKFeaturesExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKFeaturesExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        # Don't call extract() first
        result = ext.prepare_export_data("ClickHouseExporter")
        assert result is None


# ============================================================================
# APKManifestExtractor
# ============================================================================

class TestAPKManifestExtractor:

    def test_tag(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_manifest import APKManifestExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKManifestExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        assert ext.tag() == "apk_manifest"

    def test_clickhouse_table(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_manifest import APKManifestExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKManifestExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        assert ext.get_clickhouse_table() == "redb_apk_manifest"

    def test_extract_returns_dataclass(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_manifest import APKManifestExtractor
        from redb.models.dataclasses import APKManifest

        apk_path, mock_apk, logger, exporters = fake_apk_env
        # Setup intent filter mocking
        mock_apk.get_intent_filters.return_value = {}
        ext = APKManifestExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        result = ext.extract()

        assert isinstance(result, APKManifest)
        assert result.activity_count == 1
        assert result.service_count == 1
        assert result.receiver_count == 1
        assert result.provider_count == 0

    def test_extract_counts_components(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_manifest import APKManifestExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        mock_apk.get_activities.return_value = ["A1", "A2", "A3"]
        mock_apk.get_services.return_value = ["S1", "S2"]
        mock_apk.get_intent_filters.return_value = {}
        ext = APKManifestExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        result = ext.extract()
        assert result.activity_count == 3
        assert result.service_count == 2


# ============================================================================
# APKPermissionsExtractor
# ============================================================================

class TestAPKPermissionsExtractor:

    def test_tag(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_permissions import APKPermissionsExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKPermissionsExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        assert ext.tag() == "apk_permissions"

    def test_clickhouse_table(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_permissions import APKPermissionsExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKPermissionsExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        assert ext.get_clickhouse_table() == "redb_apk_permissions"

    def test_extract_returns_dataclass(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_permissions import APKPermissionsExtractor
        from redb.models.dataclasses import APKPermissions

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKPermissionsExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        result = ext.extract()

        assert isinstance(result, APKPermissions)
        assert result.total_permission_count == 2
        assert "android.permission.INTERNET" in result.permissions
        assert "android.permission.CAMERA" in result.permissions

    def test_extract_classifies_dangerous(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_permissions import APKPermissionsExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKPermissionsExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        result = ext.extract()
        # CAMERA is dangerous, INTERNET is normal
        assert result.dangerous_permission_count >= 1
        assert "android.permission.CAMERA" in result.dangerous_permissions

    def test_extract_no_permissions(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_permissions import APKPermissionsExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        mock_apk.get_permissions.return_value = []
        ext = APKPermissionsExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        result = ext.extract()
        assert result.total_permission_count == 0
        assert result.dangerous_permission_count == 0


# ============================================================================
# APKSignatureExtractor
# ============================================================================

class TestAPKSignatureExtractor:

    def test_tag(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_signature import APKSignatureExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKSignatureExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        assert ext.tag() == "apk_signature"

    def test_clickhouse_table(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_signature import APKSignatureExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKSignatureExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        assert ext.get_clickhouse_table() == "redb_apk_signature"

    def test_extract_unsigned_apk(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_signature import APKSignatureExtractor
        from redb.models.dataclasses import APKCodeSigningInfo

        apk_path, mock_apk, logger, exporters = fake_apk_env
        mock_apk.is_signed_v1.return_value = False
        mock_apk.is_signed_v2.return_value = False
        mock_apk.is_signed_v3.return_value = False
        ext = APKSignatureExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        result = ext.extract()

        assert isinstance(result, APKCodeSigningInfo)

    def test_extract_detects_signature_schemes(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_signature import APKSignatureExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        mock_apk.is_signed_v1.return_value = True
        mock_apk.is_signed_v2.return_value = True
        mock_apk.is_signed_v3.return_value = True
        ext = APKSignatureExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        result = ext.extract()
        assert 1 in result.signature_scheme_versions
        assert 2 in result.signature_scheme_versions
        assert 3 in result.signature_scheme_versions

    def test_certificate_subject_uses_short_names(self, fake_apk_env):
        """Ensure subject/issuer use RFC 4514 short names (CN=, O=) not long names."""
        from unittest.mock import MagicMock
        from asn1crypto import x509
        from redb.extractors.apk_extractors.apk_signature import APKSignatureExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env

        # Build a real asn1crypto Name so the conversion is exercised
        subject_name = x509.Name.build({
            'country_name': 'US',
            'state_or_province_name': 'California',
            'organization_name': 'Android',
            'common_name': 'Android Debug',
        })

        mock_cert = MagicMock()
        mock_cert.subject = subject_name
        mock_cert.issuer = subject_name
        mock_cert.serial_number = 12345
        mock_cert.not_valid_before = '2020-01-01 00:00:00'
        mock_cert.not_valid_after = '2030-01-01 00:00:00'
        mock_cert.hash_algo = 'sha256'
        mock_cert.public_key.bit_size = 2048
        mock_cert.dump.return_value = b'fake_cert_der_data'

        mock_apk.is_signed_v1.return_value = False
        mock_apk.is_signed_v2.return_value = True
        mock_apk.is_signed_v3.return_value = False
        mock_apk.get_certificates_v2.return_value = [mock_cert]

        ext = APKSignatureExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        result = ext.extract()

        assert result.apk_signer_subject is not None
        assert 'CN=' in result.apk_signer_subject
        assert 'O=' in result.apk_signer_subject
        assert 'Common Name' not in result.apk_signer_subject
        assert 'Organization' not in result.apk_signer_subject

        # Also check the certificate list
        assert len(result.x509_certificates) == 1
        cert = result.x509_certificates[0]
        assert 'CN=' in cert.subject
        assert 'Common Name' not in cert.subject


# ============================================================================
# APKDexExtractor
# ============================================================================

class TestAPKDexExtractor:

    def test_tag(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_dex import APKDexExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKDexExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        assert ext.tag() == "apk_dex"

    def test_clickhouse_table(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_dex import APKDexExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKDexExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        assert ext.get_clickhouse_table() == "redb_apk_dex"

    def test_extract_returns_list(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_dex import APKDexExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        # Mock DEX data access
        mock_apk.get_dex_names.return_value = ["classes.dex"]
        mock_apk.get_all_dex.return_value = [b"dex\n035\x00" + b"\x00" * 100]

        # Mock the androguard DEX parser
        mock_dex_class = MagicMock()
        mock_dex_instance = MagicMock()
        mock_dex_instance.get_classes.return_value = []
        mock_dex_instance.get_methods.return_value = []
        mock_dex_instance.get_strings.return_value = []
        mock_dex_class.return_value = mock_dex_instance

        ext = APKDexExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)

        with patch.dict('sys.modules', {'androguard.core.dex': MagicMock(DEX=mock_dex_class)}):
            result = ext.extract()

        assert result is not None
        assert isinstance(result, list)
        assert len(result) == 1
        assert result[0].filename == "classes.dex"

    def test_extract_computes_tlsh(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_dex import APKDexExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        # Need enough data for TLSH (>50 bytes of varied content)
        import os
        dex_data = os.urandom(256)
        mock_apk.get_dex_names.return_value = ["classes.dex"]
        mock_apk.get_all_dex.return_value = [dex_data]

        mock_dex_class = MagicMock()
        mock_dex_instance = MagicMock()
        mock_dex_instance.get_classes.return_value = []
        mock_dex_instance.get_methods.return_value = []
        mock_dex_instance.get_strings.return_value = []
        mock_dex_instance.get_encoded_methods.return_value = []
        mock_dex_class.return_value = mock_dex_instance

        ext = APKDexExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)

        with patch.dict('sys.modules', {'androguard.core.dex': MagicMock(DEX=mock_dex_class)}):
            result = ext.extract()

        assert result is not None
        assert result[0].tlsh is not None
        assert result[0].tlsh.startswith("T1")

    def test_extract_tlsh_none_for_small_dex(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_dex import APKDexExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        # Too small / uniform for TLSH
        dex_data = b"\x00" * 10
        mock_apk.get_dex_names.return_value = ["classes.dex"]
        mock_apk.get_all_dex.return_value = [dex_data]

        mock_dex_class = MagicMock()
        mock_dex_instance = MagicMock()
        mock_dex_instance.get_classes.return_value = []
        mock_dex_instance.get_methods.return_value = []
        mock_dex_instance.get_strings.return_value = []
        mock_dex_instance.get_encoded_methods.return_value = []
        mock_dex_class.return_value = mock_dex_instance

        ext = APKDexExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)

        with patch.dict('sys.modules', {'androguard.core.dex': MagicMock(DEX=mock_dex_class)}):
            result = ext.extract()

        assert result is not None
        # TLSH returns empty string for insufficient data, which we convert to None
        assert result[0].tlsh is None

    def test_prepare_export_clickhouse_includes_tlsh(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_dex import APKDexExtractor
        from redb.models.dataclasses import APKDexFile

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKDexExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)

        # Manually set dex_files to test export
        ext.dex_files = [APKDexFile(
            filename="classes.dex",
            sha256="aa" * 32,
            class_count=10,
            method_count=50,
            string_count=100,
            tlsh="T1" + "B" * 68,
        )]

        result = ext.prepare_export_data("ClickHouseExporter")
        assert result is not None
        dex_export = result['dex']
        assert 'dex_tlsh' in dex_export['column_names']
        tlsh_idx = dex_export['column_names'].index('dex_tlsh')
        assert dex_export['data'][0][tlsh_idx] == "T1" + "B" * 68
        assert len(dex_export['column_names']) == len(dex_export['column_type_names'])

    def test_extract_no_dex_returns_none(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_dex import APKDexExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        mock_apk.get_dex_names.return_value = []
        mock_apk.get_all_dex.return_value = []
        ext = APKDexExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        result = ext.extract()
        assert result is None


# ============================================================================
# APKResourceExtractor
# ============================================================================

class TestAPKResourceExtractor:

    def test_tag(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_resources import APKResourceExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKResourceExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        assert ext.tag() == "apk_resources"

    def test_clickhouse_table(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_resources import APKResourceExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKResourceExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        assert ext.get_clickhouse_table() == "redb_apk_resources"

    def test_extract_returns_dict(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_resources import APKResourceExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKResourceExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        result = ext.extract()
        # Returns dict with 'resources' key containing list of APKResource
        assert result is None or isinstance(result, dict)
        if result:
            assert "resources" in result
            assert "total_resource_count" in result
            assert isinstance(result["resources"], list)


# ============================================================================
# APKResourceExtractor — _identify_android_format
# ============================================================================

class TestIdentifyAndroidFormat:
    """Unit tests for Android-specific binary format magic detection."""

    def _make_extractor(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_resources import APKResourceExtractor
        apk_path, mock_apk, logger, exporters = fake_apk_env
        return APKResourceExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)

    def test_axml_magic(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        header = b'\x03\x00\x08\x00' + b'\x00' * 12
        assert ext._identify_android_format(header) == "android_binary_xml"

    def test_arsc_magic(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        header = b'\x02\x00\x0c\x00' + b'\x00' * 12
        assert ext._identify_android_format(header) == "android_resource_table"

    def test_art_prof_magic(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        header = b'pro\x00' + b'\x00' * 12
        assert ext._identify_android_format(header) == "android_art_profile"

    def test_art_profm_magic(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        header = b'prm\x00' + b'\x00' * 12
        assert ext._identify_android_format(header) == "android_art_profile_metadata"

    def test_unknown_magic_returns_none(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        header = b'\xff\xff\xff\xff' + b'\x00' * 12
        assert ext._identify_android_format(header) is None

    def test_short_header_returns_none(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._identify_android_format(b'\x03\x00') is None
        assert ext._identify_android_format(b'') is None

    def test_png_magic_not_matched(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        header = b'\x89PNG\r\n\x1a\n' + b'\x00' * 8
        assert ext._identify_android_format(header) is None


# ============================================================================
# APKResourceExtractor — _is_suspicious_resource
# ============================================================================

class TestIsSuspiciousResource:
    """Unit tests for the layered suspicious resource classification."""

    def _make_extractor(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_resources import APKResourceExtractor
        apk_path, mock_apk, logger, exporters = fake_apk_env
        return APKResourceExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)

    # ── Layer 1: Allowlisted paths ──────────────────────────────────────

    def test_allowlisted_baseline_prof_not_suspicious(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="assets/dexopt/baseline.prof",
            magika_type="unknown", entropy=7.9,
            android_type=None,
        ) is False

    def test_allowlisted_baseline_profm_not_suspicious(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="assets/dexopt/baseline.profm",
            magika_type="unknown", entropy=7.9,
            android_type=None,
        ) is False

    # ── Layer 2: Android-specific formats → always benign ───────────────

    def test_axml_not_suspicious(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="res/layout/activity_main.xml",
            magika_type="gzip", entropy=6.5,
            android_type="android_binary_xml",
        ) is False

    def test_arsc_not_suspicious(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="res/values/resources.arsc",
            magika_type="unknown", entropy=7.2,
            android_type="android_resource_table",
        ) is False

    def test_art_profile_not_suspicious(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="assets/custom/baseline.prof",
            magika_type="unknown", entropy=7.8,
            android_type="android_art_profile",
        ) is False

    def test_art_profile_metadata_not_suspicious(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="assets/other/baseline.profm",
            magika_type="unknown", entropy=7.5,
            android_type="android_art_profile_metadata",
        ) is False

    # ── Layer 3: Image extension / Magika type mismatch ─────────────────

    def test_fake_png_extension_with_non_image_content_is_suspicious(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="res/raw/payload.png",
            magika_type="zip", entropy=7.2,
            android_type=None,
        ) is True

    def test_fake_jpg_extension_with_unknown_high_entropy_is_suspicious(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="assets/data.jpg",
            magika_type="unknown", entropy=6.5,
            android_type=None,
        ) is True

    def test_image_extension_mismatch_low_entropy_not_suspicious(self, fake_apk_env):
        """Tiny/low-entropy files with image extensions shouldn't be flagged."""
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="res/drawable/tiny.png",
            magika_type="unknown", entropy=3.0,
            android_type=None,
        ) is False

    # ── Layer 4: Magika-confirmed images → benign ───────────────────────

    def test_confirmed_png_high_entropy_not_suspicious(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="res/drawable/photo.png",
            magika_type="png", entropy=7.95,
            android_type=None,
        ) is False

    def test_confirmed_webp_high_entropy_not_suspicious(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="res/drawable/image.webp",
            magika_type="webp", entropy=7.99,
            android_type=None,
        ) is False

    def test_confirmed_jpeg_not_suspicious(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="assets/photos/bg.jpg",
            magika_type="jpeg", entropy=7.9,
            android_type=None,
        ) is False

    # ── Layer 5: Known suspicious file types ────────────────────────────

    def test_elf_in_assets_is_suspicious(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="assets/payload",
            magika_type="elf", entropy=5.0,
            android_type=None,
        ) is True

    def test_dex_in_assets_is_suspicious(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="assets/classes.dex",
            magika_type="dex", entropy=5.5,
            android_type=None,
        ) is True

    def test_shell_script_is_suspicious(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="assets/run.sh",
            magika_type="shell", entropy=4.0,
            android_type=None,
        ) is True

    def test_zip_in_assets_is_suspicious(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="assets/data.zip",
            magika_type="zip", entropy=7.5,
            android_type=None,
        ) is True

    def test_jar_in_assets_is_suspicious(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="assets/plugin.jar",
            magika_type="jar", entropy=6.0,
            android_type=None,
        ) is True

    def test_lzma_in_assets_is_suspicious(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="assets/blob.lzma",
            magika_type="lzma", entropy=7.9,
            android_type=None,
        ) is True

    # ── Layer 6: High-entropy unrecognized blobs ────────────────────────

    def test_unknown_type_high_entropy_is_suspicious(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="assets/xyzdata",
            magika_type="unknown", entropy=7.5,
            android_type=None,
        ) is True

    def test_empty_type_high_entropy_is_suspicious(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="assets/blob.dat",
            magika_type="empty", entropy=7.2,
            android_type=None,
        ) is True

    def test_unknown_type_low_entropy_not_suspicious(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="assets/config.dat",
            magika_type="unknown", entropy=4.5,
            android_type=None,
        ) is False

    # ── Layer 7: Extreme entropy on non-image recognized types ──────────

    def test_xml_extreme_entropy_is_suspicious(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="res/raw/config.xml",
            magika_type="xml", entropy=7.9,
            android_type=None,
        ) is True

    def test_txt_extreme_entropy_is_suspicious(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="assets/readme.txt",
            magika_type="txt", entropy=7.86,
            android_type=None,
        ) is True

    def test_xml_normal_entropy_not_suspicious(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="res/raw/config.xml",
            magika_type="xml", entropy=4.0,
            android_type=None,
        ) is False

    # ── Benign types that should NOT be flagged ─────────────────────────

    def test_html_not_in_suspicious_types(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="assets/index.html",
            magika_type="html", entropy=5.0,
            android_type=None,
        ) is False

    def test_javascript_not_in_suspicious_types(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="assets/app.js",
            magika_type="javascript", entropy=5.0,
            android_type=None,
        ) is False

    def test_sqlite_not_in_suspicious_types(self, fake_apk_env):
        """sqlite was intentionally excluded — too many legitimate uses."""
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="assets/data.db",
            magika_type="sqlite", entropy=5.0,
            android_type=None,
        ) is False


# ============================================================================
# APKResourceExtractor — integration with extract()
# ============================================================================

class TestAPKResourceExtractorIntegration:
    """Integration tests: craft ZIP contents and verify end-to-end classification."""

    def test_extract_flags_elf_in_assets(self, tmp_path):
        from redb.extractors.apk_extractors.apk_resources import APKResourceExtractor

        apk_path = _create_fake_apk(str(tmp_path), {
            "assets/payload.bin": b"\x7fELF" + b"\x00" * 100,
        })
        mock_apk = _make_mock_apk()
        logger = MagicMock()

        # Mock Magika to return "elf" for the payload — real Magika needs
        # a valid ELF structure, but we control classification in unit tests.
        mock_magika = MagicMock()
        mock_magika.identify_bytes.return_value = MagicMock(
            output=MagicMock(label="elf")
        )

        ext = APKResourceExtractor(apk_path, logger, exporters=[MagicMock()], apk=mock_apk)
        with patch("magika.Magika", return_value=mock_magika):
            result = ext.extract()
        assert result is not None
        assert result["suspicious_file_count"] >= 1
        sus_paths = [s.path for s in result["suspicious_files"]]
        assert "assets/payload.bin" in sus_paths

    def test_extract_does_not_flag_normal_png(self, tmp_path):
        from redb.extractors.apk_extractors.apk_resources import APKResourceExtractor

        apk_path = _create_fake_apk(str(tmp_path), {
            "res/drawable/icon.png": b"\x89PNG\r\n\x1a\n" + b"\x00" * 50,
        })
        mock_apk = _make_mock_apk()
        logger = MagicMock()

        ext = APKResourceExtractor(apk_path, logger, exporters=[MagicMock()], apk=mock_apk)
        result = ext.extract()
        assert result is not None
        sus_paths = [s.path for s in result["suspicious_files"]]
        assert "res/drawable/icon.png" not in sus_paths

    def test_extract_stores_android_type_override(self, tmp_path):
        from redb.extractors.apk_extractors.apk_resources import APKResourceExtractor

        # AXML magic — Magika would likely misclassify, but stored_type
        # should be "android_binary_xml"
        axml_data = b'\x03\x00\x08\x00' + b'\x00' * 50
        apk_path = _create_fake_apk(str(tmp_path), {
            "res/layout/main.xml": axml_data,
        })
        mock_apk = _make_mock_apk()
        logger = MagicMock()

        ext = APKResourceExtractor(apk_path, logger, exporters=[MagicMock()], apk=mock_apk)
        result = ext.extract()
        assert result is not None
        axml_resources = [r for r in result["resources"]
                          if r.path == "res/layout/main.xml"]
        assert len(axml_resources) == 1
        assert axml_resources[0].filetype_magika == "android_binary_xml"
        assert axml_resources[0].is_suspicious is False

    def test_extract_skips_non_res_non_assets(self, tmp_path):
        from redb.extractors.apk_extractors.apk_resources import APKResourceExtractor

        apk_path = _create_fake_apk(str(tmp_path), {
            "lib/armeabi-v7a/libnative.so": b"\x7fELF" + b"\x00" * 50,
            "META-INF/MANIFEST.MF": b"Manifest-Version: 1.0\n",
        })
        mock_apk = _make_mock_apk()
        logger = MagicMock()

        ext = APKResourceExtractor(apk_path, logger, exporters=[MagicMock()], apk=mock_apk)
        result = ext.extract()
        if result:
            res_paths = [r.path for r in result["resources"]]
            assert "lib/armeabi-v7a/libnative.so" not in res_paths
            assert "META-INF/MANIFEST.MF" not in res_paths

    def test_prepare_export_includes_suspicious_flag(self, tmp_path):
        from redb.extractors.apk_extractors.apk_resources import APKResourceExtractor

        apk_path = _create_fake_apk(str(tmp_path), {
            "assets/evil.bin": b"\x7fELF" + b"\x00" * 100,
            "res/drawable/ok.png": b"\x89PNG\r\n\x1a\n" + b"\x00" * 50,
        })
        mock_apk = _make_mock_apk()
        logger = MagicMock()

        # Map filenames to Magika labels so the ELF payload gets flagged
        def _magika_side_effect(data):
            if data[:4] == b"\x7fELF":
                return MagicMock(output=MagicMock(label="elf"))
            return MagicMock(output=MagicMock(label="png"))

        mock_magika = MagicMock()
        mock_magika.identify_bytes.side_effect = _magika_side_effect

        ext = APKResourceExtractor(apk_path, logger, exporters=[MagicMock()], apk=mock_apk)
        with patch("magika.Magika", return_value=mock_magika):
            ext.extract()
        result = ext.prepare_export_data("ClickHouseExporter")
        assert result is not None
        data, col_names, col_types = result
        assert "is_suspicious" in col_names
        sus_idx = col_names.index("is_suspicious")
        # At least one row should have is_suspicious=1
        sus_values = [row[sus_idx] for row in data]
        assert 1 in sus_values


# ============================================================================
# APKNativeLibExtractor
# ============================================================================

class TestAPKNativeLibExtractor:

    def test_tag(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_native_libs import APKNativeLibExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKNativeLibExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        assert ext.tag() == "apk_native_libs"

    def test_clickhouse_table(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_native_libs import APKNativeLibExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKNativeLibExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        assert ext.get_clickhouse_table() == "redb_apk_native_libs"

    def test_extract_finds_native_libs(self, tmp_path):
        from redb.extractors.apk_extractors.apk_native_libs import APKNativeLibExtractor
        from redb.models.dataclasses import APKNativeLib

        # Create APK with a native lib inside
        apk_path = _create_fake_apk(str(tmp_path), {
            "lib/armeabi-v7a/libnative.so": b"\x7fELF" + b"\x00" * 100,
        })
        mock_apk = _make_mock_apk()
        logger = MagicMock()
        exporters = [MagicMock()]

        ext = APKNativeLibExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        result = ext.extract()
        assert result is not None
        assert isinstance(result, dict)
        assert "native_libs" in result
        assert len(result["native_libs"]) >= 1
        assert isinstance(result["native_libs"][0], APKNativeLib)
        assert result["native_libs"][0].abi == "armeabi-v7a"

    def test_extract_detects_packer_libs(self, tmp_path):
        from redb.extractors.apk_extractors.apk_native_libs import APKNativeLibExtractor

        # Create APK with a known packer lib
        apk_path = _create_fake_apk(str(tmp_path), {
            "lib/armeabi-v7a/libjiagu.so": b"\x7fELF" + b"\x00" * 50,
            "lib/armeabi-v7a/libnormal.so": b"\x7fELF" + b"\x00" * 50,
        })
        mock_apk = _make_mock_apk()
        logger = MagicMock()
        exporters = [MagicMock()]

        ext = APKNativeLibExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        result = ext.extract()
        assert result is not None
        packer_libs = [lib for lib in result["native_libs"] if lib.is_known_packer]
        assert len(packer_libs) >= 1  # libjiagu.so is a known packer

    def test_extract_no_native_libs(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_native_libs import APKNativeLibExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        # The fake APK has no lib/*.so files
        mock_apk.get_files.return_value = ["classes.dex", "AndroidManifest.xml"]
        ext = APKNativeLibExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        result = ext.extract()
        assert result is None


# ============================================================================
# APKInconsistencyTestsExtractor
# ============================================================================

class TestAPKInconsistencyTestsExtractor:

    def test_tag(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_inconsistency_tests import APKInconsistencyTestsExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKInconsistencyTestsExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        assert ext.tag() == "apk_inconsistency_tests"

    def test_clickhouse_table(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_inconsistency_tests import APKInconsistencyTestsExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKInconsistencyTestsExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        assert ext.get_clickhouse_table() == "redb_apk_inconsistency_tests"

    def test_extract_returns_dataclass(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_inconsistency_tests import APKInconsistencyTestsExtractor
        from redb.models.dataclasses import APKInconsistencyTests

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKInconsistencyTestsExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        result = ext.extract()

        assert isinstance(result, APKInconsistencyTests)

    def test_extract_clean_apk_no_anomalies(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_inconsistency_tests import APKInconsistencyTestsExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKInconsistencyTestsExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        result = ext.extract()
        # A clean APK should have mostly False values
        assert result.test_zip_bomb is not None  # Should be set (True or False)


# ============================================================================
# APK workers.py integration — module registry
# ============================================================================

class TestAPKModuleRegistry:

    def test_all_apk_modules_in_registry(self):
        from redb.workers import get_module_by_name

        apk_modules = [
            "APKFeaturesExtractor",
            "APKManifestExtractor",
            "APKPermissionsExtractor",
            "APKSignatureExtractor",
            "APKDexExtractor",
            "APKResourceExtractor",
            "APKNativeLibExtractor",
            "APKInconsistencyTestsExtractor",
        ]
        for name in apk_modules:
            module_class = get_module_by_name(name)
            assert module_class is not None, f"{name} not found in module registry"

    def test_apk_module_classes_correct(self):
        from redb.workers import get_module_by_name
        from redb.extractors.apk_extractors import (
            APKFeaturesExtractor,
            APKManifestExtractor,
            APKPermissionsExtractor,
            APKSignatureExtractor,
            APKDexExtractor,
            APKResourceExtractor,
            APKNativeLibExtractor,
            APKInconsistencyTestsExtractor,
        )

        assert get_module_by_name("APKFeaturesExtractor") is APKFeaturesExtractor
        assert get_module_by_name("APKManifestExtractor") is APKManifestExtractor
        assert get_module_by_name("APKPermissionsExtractor") is APKPermissionsExtractor
        assert get_module_by_name("APKSignatureExtractor") is APKSignatureExtractor
        assert get_module_by_name("APKDexExtractor") is APKDexExtractor
        assert get_module_by_name("APKResourceExtractor") is APKResourceExtractor
        assert get_module_by_name("APKNativeLibExtractor") is APKNativeLibExtractor
        assert get_module_by_name("APKInconsistencyTestsExtractor") is APKInconsistencyTestsExtractor


# ============================================================================
# APK filetype guard — workers.py dispatch
# ============================================================================

class TestAPKWorkerDispatch:

    def test_apk_excluded_from_die_guard(self):
        """APK filetype should be in the exclusion set alongside macho for DIE/BasicProperties/Hash."""
        import redb.workers as workers_mod
        import inspect

        source = inspect.getsource(workers_mod.process_binary_file)
        # The guards should exclude "apk" from DIE, BasicProperties, HashExtractor.
        # The exact tuple may grow as new text-based formats are added (e.g.
        # "javascript"), so we check that "apk" is present in a filetype
        # exclusion guard rather than matching the full tuple literally.
        assert 'filetype not in ("macho", "apk"' in source

    def test_apk_dispatch_branch_exists(self):
        """The APK dispatch branch should exist and registry should provide all APK extractors."""
        import inspect
        import redb.workers as workers_mod
        from redb.extractor_registry import get_filetype_modules

        # Verify the APK branch exists in process_binary_file
        source = inspect.getsource(workers_mod.process_binary_file)
        assert 'elif filetype == "apk"' in source

        # Verify all APK extractors are registered and loadable
        apk_modules = get_filetype_modules("apk")
        apk_names = {m.__name__ for m in apk_modules}
        for expected in [
            "APKFeaturesExtractor", "APKManifestExtractor",
            "APKPermissionsExtractor", "APKSignatureExtractor",
            "APKDexExtractor", "APKResourceExtractor",
            "APKNativeLibExtractor", "APKInconsistencyTestsExtractor",
        ]:
            assert expected in apk_names, f"{expected} not in APK registry"

    def test_apk_dispatch_runs_basic_and_hash(self):
        """The APK dispatch branch should run BasicPropertiesExtractor and HashExtractor."""
        import redb.workers as workers_mod
        import inspect

        source = inspect.getsource(workers_mod.process_binary_file)
        # Find the apk block
        apk_idx = source.index('elif filetype == "apk"')
        apk_block = source[apk_idx:apk_idx + 4000]
        assert "BasicPropertiesExtractor" in apk_block
        assert "HashExtractor" in apk_block


# ============================================================================
# APKExtractor base class
# ============================================================================

class TestAPKExtractorBase:

    def test_binary_property(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_features import APKFeaturesExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKFeaturesExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)

        # binary property should return the file content
        with open(apk_path, "rb") as f:
            expected = f.read()
        assert ext.binary == expected

    def test_is_valid_apk_with_apk(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_features import APKFeaturesExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKFeaturesExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        assert ext._is_valid_apk() is True

    def test_is_valid_apk_without_apk(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_features import APKFeaturesExtractor

        apk_path, _, logger, exporters = fake_apk_env
        ext = APKFeaturesExtractor(apk_path, logger, exporters=exporters, apk=None)
        ext.apk = None
        assert ext._is_valid_apk() is False

    def test_list_files(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_features import APKFeaturesExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKFeaturesExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        files = ext._list_files()
        assert len(files) == 5
        assert "classes.dex" in files

    def test_get_zip_file(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_features import APKFeaturesExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKFeaturesExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        zf = ext._get_zip_file()
        assert zf is not None
        zf.close()

    def test_sha256_computed_correctly(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_features import APKFeaturesExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKFeaturesExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)

        # Verify sha256 matches actual file content
        with open(apk_path, "rb") as f:
            expected_sha256 = hashlib.sha256(f.read()).hexdigest()
        assert ext.sha256 == expected_sha256