first commit
This commit is contained in:
196
pkg/android_binary/apk/apk.go
Normal file
196
pkg/android_binary/apk/apk.go
Normal file
@ -0,0 +1,196 @@
|
||||
package apk
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"git.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))
|
||||
}
|
Reference in New Issue
Block a user