base-golang/pkg/android_binary/apk/apk.go
2024-07-31 16:49:14 +08:00

197 lines
4.3 KiB
Go

package apk
import (
"archive/zip"
"bytes"
"fmt"
"gitea.bvbej.com/bvbej/base-golang/pkg/android_binary"
"image"
"io"
"os"
"strconv"
_ "image/jpeg" // handle jpeg format
_ "image/png" // handle png format
)
// Apk is an application package file for android.
type Apk struct {
f *os.File
zipreader *zip.Reader
manifest Manifest
table *android_binary.TableFile
}
// OpenFile will open the file specified by filename and return Apk
func OpenFile(filename string) (apk *Apk, err error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer func() {
if err != nil {
f.Close()
}
}()
fi, err := f.Stat()
if err != nil {
return nil, err
}
apk, err = OpenZipReader(f, fi.Size())
if err != nil {
return nil, err
}
apk.f = f
return
}
// OpenZipReader has same arguments like zip.NewReader
func OpenZipReader(r io.ReaderAt, size int64) (*Apk, error) {
zipreader, err := zip.NewReader(r, size)
if err != nil {
return nil, err
}
apk := &Apk{
zipreader: zipreader,
}
if err = apk.parseResources(); err != nil {
return nil, err
}
if err = apk.parseManifest(); err != nil {
return nil, errorf("parse-manifest: %w", err)
}
return apk, nil
}
// Close is avaliable only if apk is created with OpenFile
func (k *Apk) Close() error {
if k.f == nil {
return nil
}
return k.f.Close()
}
// Icon returns the icon image of the APK.
func (k *Apk) Icon(resConfig *android_binary.ResTableConfig) (image.Image, error) {
iconPath, err := k.manifest.App.Icon.WithResTableConfig(resConfig).String()
if err != nil {
return nil, err
}
if android_binary.IsResID(iconPath) {
return nil, newError("unable to convert icon-id to icon path")
}
imgData, err := k.readZipFile(iconPath)
if err != nil {
return nil, err
}
m, _, err := image.Decode(bytes.NewReader(imgData))
return m, err
}
// Label returns the label of the APK.
func (k *Apk) Label(resConfig *android_binary.ResTableConfig) (s string, err error) {
s, err = k.manifest.App.Label.WithResTableConfig(resConfig).String()
if err != nil {
return
}
if android_binary.IsResID(s) {
err = newError("unable to convert label-id to string")
}
return
}
// Manifest returns the manifest of the APK.
func (k *Apk) Manifest() Manifest {
return k.manifest
}
// PackageName returns the package name of the APK.
func (k *Apk) PackageName() string {
return k.manifest.Package.MustString()
}
func isMainIntentFilter(intent ActivityIntentFilter) bool {
ok := false
for _, action := range intent.Actions {
s, err := action.Name.String()
if err == nil && s == "android.intent.action.MAIN" {
ok = true
break
}
}
if !ok {
return false
}
ok = false
for _, category := range intent.Categories {
s, err := category.Name.String()
if err == nil && s == "android.intent.category.LAUNCHER" {
ok = true
break
}
}
return ok
}
// MainActivity returns the name of the main activity.
func (k *Apk) MainActivity() (activity string, err error) {
for _, act := range k.manifest.App.Activities {
for _, intent := range act.IntentFilters {
if isMainIntentFilter(intent) {
return act.Name.String()
}
}
}
for _, act := range k.manifest.App.ActivityAliases {
for _, intent := range act.IntentFilters {
if isMainIntentFilter(intent) {
return act.TargetActivity.String()
}
}
}
return "", newError("No main activity found")
}
func (k *Apk) parseManifest() error {
xmlData, err := k.readZipFile("AndroidManifest.xml")
if err != nil {
return errorf("failed to read AndroidManifest.xml: %w", err)
}
xmlfile, err := android_binary.NewXMLFile(bytes.NewReader(xmlData))
if err != nil {
return errorf("failed to parse AndroidManifest.xml: %w", err)
}
return xmlfile.Decode(&k.manifest, k.table, nil)
}
func (k *Apk) parseResources() (err error) {
resData, err := k.readZipFile("resources.arsc")
if err != nil {
return
}
k.table, err = android_binary.NewTableFile(bytes.NewReader(resData))
return
}
func (k *Apk) readZipFile(name string) (data []byte, err error) {
buf := bytes.NewBuffer(nil)
for _, file := range k.zipreader.File {
if file.Name != name {
continue
}
rc, er := file.Open()
if er != nil {
err = er
return
}
defer rc.Close()
_, err = io.Copy(buf, rc)
if err != nil {
return
}
return buf.Bytes(), nil
}
return nil, fmt.Errorf("File %s not found", strconv.Quote(name))
}