base-golang/pkg/android_binary/table.go
2024-07-23 10:23:43 +08:00

1094 lines
25 KiB
Go

package android_binary
import (
"encoding/binary"
"fmt"
"io"
"strconv"
"strings"
"unsafe"
)
// ResID is ID for resources.
type ResID uint32
// TableFile is a resource table file.
type TableFile struct {
stringPool *ResStringPool
tablePackages map[uint32]*TablePackage
}
// ResTableHeader is a header of TableFile.
type ResTableHeader struct {
Header ResChunkHeader
PackageCount uint32
}
// ResTablePackage is a header of table packages.
type ResTablePackage struct {
Header ResChunkHeader
ID uint32
Name [128]uint16
TypeStrings uint32
LastPublicType uint32
KeyStrings uint32
LastPublicKey uint32
}
// TablePackage is a table package.
type TablePackage struct {
Header ResTablePackage
TypeStrings *ResStringPool
KeyStrings *ResStringPool
TableTypes []*TableType
}
// ResTableType is a type of a table.
type ResTableType struct {
Header ResChunkHeader
ID uint8
Res0 uint8
Res1 uint16
EntryCount uint32
EntriesStart uint32
Config ResTableConfig
}
// ScreenLayout describes screen layout.
type ScreenLayout uint8
// ScreenLayout bits
const (
MaskScreenSize ScreenLayout = 0x0f
ScreenSizeAny ScreenLayout = 0x01
ScreenSizeSmall ScreenLayout = 0x02
ScreenSizeNormal ScreenLayout = 0x03
ScreenSizeLarge ScreenLayout = 0x04
ScreenSizeXLarge ScreenLayout = 0x05
MaskScreenLong ScreenLayout = 0x30
ShiftScreenLong = 4
ScreenLongAny ScreenLayout = 0x00
ScreenLongNo ScreenLayout = 0x10
ScreenLongYes ScreenLayout = 0x20
MaskLayoutDir ScreenLayout = 0xC0
ShiftLayoutDir = 6
LayoutDirAny ScreenLayout = 0x00
LayoutDirLTR ScreenLayout = 0x40
LayoutDirRTL ScreenLayout = 0x80
)
// UIMode describes UI mode.
type UIMode uint8
// UIMode bits
const (
MaskUIModeType UIMode = 0x0f
UIModeTypeAny UIMode = 0x01
UIModeTypeNormal UIMode = 0x02
UIModeTypeDesk UIMode = 0x03
UIModeTypeCar UIMode = 0x04
MaskUIModeNight UIMode = 0x30
ShiftUIModeNight = 4
UIModeNightAny UIMode = 0x00
UIModeNightNo UIMode = 0x10
UIModeNightYes UIMode = 0x20
)
// InputFlags are input flags.
type InputFlags uint8
// input flags
const (
MaskKeysHidden InputFlags = 0x03
KeysHiddenAny InputFlags = 0x00
KeysHiddenNo InputFlags = 0x01
KeysHiddenYes InputFlags = 0x02
KeysHiddenSoft InputFlags = 0x03
MaskNavHidden InputFlags = 0x0c
NavHiddenAny InputFlags = 0x00
NavHiddenNo InputFlags = 0x04
NavHiddenYes InputFlags = 0x08
)
// ResTableConfig is a configuration of a table.
type ResTableConfig struct {
Size uint32
// imsi
Mcc uint16
Mnc uint16
// locale
Language [2]uint8
Country [2]uint8
// screen type
Orientation uint8
Touchscreen uint8
Density uint16
// inout
Keyboard uint8
Navigation uint8
InputFlags InputFlags
InputPad0 uint8
// screen size
ScreenWidth uint16
ScreenHeight uint16
// version
SDKVersion uint16
MinorVersion uint16
// screen config
ScreenLayout ScreenLayout
UIMode UIMode
SmallestScreenWidthDp uint16
// screen size dp
ScreenWidthDp uint16
ScreenHeightDp uint16
}
// TableType is a collection of resource entries for a particular resource data type.
type TableType struct {
Header *ResTableType
Entries []TableEntry
}
// ResTableEntry is the beginning of information about an entry in the resource table.
type ResTableEntry struct {
Size uint16
Flags uint16
Key ResStringPoolRef
}
// TableEntry is a entry in a resource table.
type TableEntry struct {
Key *ResTableEntry
Value *ResValue
Flags uint32
}
// ResTableTypeSpec is specification of the resources defined by a particular type.
type ResTableTypeSpec struct {
Header ResChunkHeader
ID uint8
Res0 uint8
Res1 uint16
EntryCount uint32
}
// IsResID returns whether s is ResId.
func IsResID(s string) bool {
return strings.HasPrefix(s, "@0x")
}
// ParseResID parses ResId.
func ParseResID(s string) (ResID, error) {
if !IsResID(s) {
return 0, fmt.Errorf("androidbinary: %s is not ResID", s)
}
id, err := strconv.ParseUint(s[3:], 16, 32)
if err != nil {
return 0, err
}
return ResID(id), nil
}
func (id ResID) String() string {
return fmt.Sprintf("@0x%08X", uint32(id))
}
// Package returns the package index of id.
func (id ResID) Package() uint32 {
return uint32(id) >> 24
}
// Type returns the type index of id.
func (id ResID) Type() int {
return (int(id) >> 16) & 0xFF
}
// Entry returns the entry index of id.
func (id ResID) Entry() int {
return int(id) & 0xFFFF
}
// NewTableFile returns new TableFile.
func NewTableFile(r io.ReaderAt) (*TableFile, error) {
f := new(TableFile)
sr := io.NewSectionReader(r, 0, 1<<63-1)
header := new(ResTableHeader)
binary.Read(sr, binary.LittleEndian, header)
f.tablePackages = make(map[uint32]*TablePackage)
offset := int64(header.Header.HeaderSize)
for offset < int64(header.Header.Size) {
chunkHeader, err := f.readChunk(sr, offset)
if err != nil {
return nil, err
}
offset += int64(chunkHeader.Size)
}
return f, nil
}
func (f *TableFile) findPackage(id uint32) *TablePackage {
if f == nil {
return nil
}
return f.tablePackages[id]
}
func (p *TablePackage) findEntry(typeIndex, entryIndex int, config *ResTableConfig) TableEntry {
var best *TableType
for _, t := range p.TableTypes {
switch {
case int(t.Header.ID) != typeIndex:
// nothing to do
case !t.Header.Config.Match(config):
// nothing to do
case entryIndex >= len(t.Entries):
// nothing to do
case t.Entries[entryIndex].Value == nil:
// nothing to do
case best == nil || t.Header.Config.IsBetterThan(&best.Header.Config, config):
best = t
}
}
if best == nil || entryIndex >= len(best.Entries) {
return TableEntry{}
}
return best.Entries[entryIndex]
}
// GetResource returns a resource referenced by id.
func (f *TableFile) GetResource(id ResID, config *ResTableConfig) (any, error) {
p := f.findPackage(id.Package())
if p == nil {
return nil, fmt.Errorf("androidbinary: package 0x%02X not found", id.Package())
}
e := p.findEntry(id.Type(), id.Entry(), config)
v := e.Value
if v == nil {
return nil, fmt.Errorf("androidbinary: entry 0x%04X not found", id.Entry())
}
switch v.DataType {
case TypeNull:
return nil, nil
case TypeString:
return f.GetString(ResStringPoolRef(v.Data)), nil
case TypeIntDec:
return v.Data, nil
case TypeIntHex:
return v.Data, nil
case TypeIntBoolean:
return v.Data != 0, nil
}
return v.Data, nil
}
// GetString returns a string referenced by ref.
func (f *TableFile) GetString(ref ResStringPoolRef) string {
return f.stringPool.GetString(ref)
}
func (f *TableFile) readChunk(r io.ReaderAt, offset int64) (*ResChunkHeader, error) {
sr := io.NewSectionReader(r, offset, 1<<63-1-offset)
chunkHeader := &ResChunkHeader{}
if _, err := sr.Seek(0, io.SeekStart); err != nil {
return nil, err
}
if err := binary.Read(sr, binary.LittleEndian, chunkHeader); err != nil {
return nil, err
}
var err error
if _, err := sr.Seek(0, io.SeekStart); err != nil {
return nil, err
}
switch chunkHeader.Type {
case ResStringPoolChunkType:
f.stringPool, err = readStringPool(sr)
case ResTablePackageType:
var tablePackage *TablePackage
tablePackage, err = readTablePackage(sr)
f.tablePackages[tablePackage.Header.ID] = tablePackage
}
if err != nil {
return nil, err
}
return chunkHeader, nil
}
func readTablePackage(sr *io.SectionReader) (*TablePackage, error) {
tablePackage := new(TablePackage)
header := new(ResTablePackage)
if err := binary.Read(sr, binary.LittleEndian, header); err != nil {
return nil, err
}
tablePackage.Header = *header
srTypes := io.NewSectionReader(sr, int64(header.TypeStrings), int64(header.Header.Size-header.TypeStrings))
if typeStrings, err := readStringPool(srTypes); err == nil {
tablePackage.TypeStrings = typeStrings
} else {
return nil, err
}
srKeys := io.NewSectionReader(sr, int64(header.KeyStrings), int64(header.Header.Size-header.KeyStrings))
if keyStrings, err := readStringPool(srKeys); err == nil {
tablePackage.KeyStrings = keyStrings
} else {
return nil, err
}
offset := int64(header.Header.HeaderSize)
for offset < int64(header.Header.Size) {
chunkHeader := &ResChunkHeader{}
if _, err := sr.Seek(offset, io.SeekStart); err != nil {
return nil, err
}
if err := binary.Read(sr, binary.LittleEndian, chunkHeader); err != nil {
return nil, err
}
var err error
chunkReader := io.NewSectionReader(sr, offset, int64(chunkHeader.Size))
if _, err := sr.Seek(offset, io.SeekStart); err != nil {
return nil, err
}
switch chunkHeader.Type {
case ResTableTypeType:
var tableType *TableType
tableType, err = readTableType(chunkHeader, chunkReader)
tablePackage.TableTypes = append(tablePackage.TableTypes, tableType)
case ResTableTypeSpecType:
_, err = readTableTypeSpec(chunkReader)
}
if err != nil {
return nil, err
}
offset += int64(chunkHeader.Size)
}
return tablePackage, nil
}
func readTableType(chunkHeader *ResChunkHeader, sr *io.SectionReader) (*TableType, error) {
// TableType header may be omitted
header := new(ResTableType)
if _, err := sr.Seek(0, io.SeekStart); err != nil {
return nil, err
}
buf, err := newZeroFilledReader(sr, int64(chunkHeader.HeaderSize), int64(unsafe.Sizeof(*header)))
if err != nil {
return nil, err
}
if err := binary.Read(buf, binary.LittleEndian, header); err != nil {
return nil, err
}
entryIndexes := make([]uint32, header.EntryCount)
if _, err := sr.Seek(int64(header.Header.HeaderSize), io.SeekStart); err != nil {
return nil, err
}
if err := binary.Read(sr, binary.LittleEndian, entryIndexes); err != nil {
return nil, err
}
entries := make([]TableEntry, header.EntryCount)
for i, index := range entryIndexes {
if index == 0xFFFFFFFF {
continue
}
if _, err := sr.Seek(int64(header.EntriesStart+index), io.SeekStart); err != nil {
return nil, err
}
var key ResTableEntry
binary.Read(sr, binary.LittleEndian, &key)
entries[i].Key = &key
var val ResValue
binary.Read(sr, binary.LittleEndian, &val)
entries[i].Value = &val
}
return &TableType{
header,
entries,
}, nil
}
func readTableTypeSpec(sr *io.SectionReader) ([]uint32, error) {
header := new(ResTableTypeSpec)
if err := binary.Read(sr, binary.LittleEndian, header); err != nil {
return nil, err
}
flags := make([]uint32, header.EntryCount)
if _, err := sr.Seek(int64(header.Header.HeaderSize), io.SeekStart); err != nil {
return nil, err
}
if err := binary.Read(sr, binary.LittleEndian, flags); err != nil {
return nil, err
}
return flags, nil
}
// IsMoreSpecificThan returns true if c is more specific than o.
func (c *ResTableConfig) IsMoreSpecificThan(o *ResTableConfig) bool {
// nil ResTableConfig is never more specific than any ResTableConfig
if c == nil {
return false
}
if o == nil {
return false
}
// imsi
if c.Mcc != o.Mcc {
if c.Mcc == 0 {
return false
}
if o.Mnc == 0 {
return true
}
}
if c.Mnc != o.Mnc {
if c.Mnc == 0 {
return false
}
if o.Mnc == 0 {
return true
}
}
// locale
if diff := c.IsLocaleMoreSpecificThan(o); diff < 0 {
return false
} else if diff > 0 {
return true
}
// screen layout
if c.ScreenLayout != 0 || o.ScreenLayout != 0 {
if ((c.ScreenLayout ^ o.ScreenLayout) & MaskLayoutDir) != 0 {
if (c.ScreenLayout & MaskLayoutDir) == 0 {
return false
}
if (o.ScreenLayout & MaskLayoutDir) == 0 {
return true
}
}
}
// smallest screen width dp
if c.SmallestScreenWidthDp != 0 || o.SmallestScreenWidthDp != 0 {
if c.SmallestScreenWidthDp != o.SmallestScreenWidthDp {
if c.SmallestScreenWidthDp == 0 {
return false
}
if o.SmallestScreenWidthDp == 0 {
return true
}
}
}
// screen size dp
if c.ScreenWidthDp != 0 || o.ScreenWidthDp != 0 ||
c.ScreenHeightDp != 0 || o.ScreenHeightDp != 0 {
if c.ScreenWidthDp != o.ScreenWidthDp {
if c.ScreenWidthDp == 0 {
return false
}
if o.ScreenWidthDp == 0 {
return true
}
}
if c.ScreenHeightDp != o.ScreenHeightDp {
if c.ScreenHeightDp == 0 {
return false
}
if o.ScreenHeightDp == 0 {
return true
}
}
}
// screen layout
if c.ScreenLayout != 0 || o.ScreenLayout != 0 {
if ((c.ScreenLayout ^ o.ScreenLayout) & MaskScreenSize) != 0 {
if (c.ScreenLayout & MaskScreenSize) == 0 {
return false
}
if (o.ScreenLayout & MaskScreenSize) == 0 {
return true
}
}
if ((c.ScreenLayout ^ o.ScreenLayout) & MaskScreenLong) != 0 {
if (c.ScreenLayout & MaskScreenLong) == 0 {
return false
}
if (o.ScreenLayout & MaskScreenLong) == 0 {
return true
}
}
}
// orientation
if c.Orientation != o.Orientation {
if c.Orientation == 0 {
return false
}
if o.Orientation == 0 {
return true
}
}
// uimode
if c.UIMode != 0 || o.UIMode != 0 {
diff := c.UIMode ^ o.UIMode
if (diff & MaskUIModeType) != 0 {
if (c.UIMode & MaskUIModeType) == 0 {
return false
}
if (o.UIMode & MaskUIModeType) == 0 {
return true
}
}
if (diff & MaskUIModeNight) != 0 {
if (c.UIMode & MaskUIModeNight) == 0 {
return false
}
if (o.UIMode & MaskUIModeNight) == 0 {
return true
}
}
}
// touchscreen
if c.Touchscreen != o.Touchscreen {
if c.Touchscreen == 0 {
return false
}
if o.Touchscreen == 0 {
return true
}
}
// input
if c.InputFlags != 0 || o.InputFlags != 0 {
myKeysHidden := c.InputFlags & MaskKeysHidden
oKeysHidden := o.InputFlags & MaskKeysHidden
if (myKeysHidden ^ oKeysHidden) != 0 {
if myKeysHidden == 0 {
return false
}
if oKeysHidden == 0 {
return true
}
}
myNavHidden := c.InputFlags & MaskNavHidden
oNavHidden := o.InputFlags & MaskNavHidden
if (myNavHidden ^ oNavHidden) != 0 {
if myNavHidden == 0 {
return false
}
if oNavHidden == 0 {
return true
}
}
}
if c.Keyboard != o.Keyboard {
if c.Keyboard == 0 {
return false
}
if o.Keyboard == 0 {
return true
}
}
if c.Navigation != o.Navigation {
if c.Navigation == 0 {
return false
}
if o.Navigation == 0 {
return true
}
}
// screen size
if c.ScreenWidth != 0 || o.ScreenWidth != 0 ||
c.ScreenHeight != 0 || o.ScreenHeight != 0 {
if c.ScreenWidth != o.ScreenWidth {
if c.ScreenWidth == 0 {
return false
}
if o.ScreenWidth == 0 {
return true
}
}
if c.ScreenHeight != o.ScreenHeight {
if c.ScreenHeight == 0 {
return false
}
if o.ScreenHeight == 0 {
return true
}
}
}
//version
if c.SDKVersion != o.SDKVersion {
if c.SDKVersion == 0 {
return false
}
if o.SDKVersion == 0 {
return true
}
}
if c.MinorVersion != o.MinorVersion {
if c.MinorVersion == 0 {
return false
}
if o.MinorVersion == 0 {
return true
}
}
return false
}
// IsBetterThan returns true if c is better than o for the r configuration.
func (c *ResTableConfig) IsBetterThan(o *ResTableConfig, r *ResTableConfig) bool {
if r == nil {
return c.IsMoreSpecificThan(o)
}
// nil ResTableConfig is never better than any ResTableConfig
if c == nil {
return false
}
if o == nil {
return false
}
// imsi
if c.Mcc != 0 || c.Mnc != 0 || o.Mcc != 0 || o.Mnc != 0 {
if c.Mcc != o.Mcc && r.Mcc != 0 {
return c.Mcc != 0
}
if c.Mnc != o.Mnc && r.Mnc != 0 {
return c.Mnc != 0
}
}
// locale
if c.IsLocaleBetterThan(o, r) {
return true
}
// screen layout
if c.ScreenLayout != 0 || o.ScreenLayout != 0 {
myLayoutdir := c.ScreenLayout & MaskLayoutDir
oLayoutdir := o.ScreenLayout & MaskLayoutDir
if (myLayoutdir^oLayoutdir) != 0 && (r.ScreenLayout&MaskLayoutDir) != 0 {
return myLayoutdir > oLayoutdir
}
}
// smallest screen width dp
if c.SmallestScreenWidthDp != 0 || o.SmallestScreenWidthDp != 0 {
if c.SmallestScreenWidthDp != o.SmallestScreenWidthDp {
return c.SmallestScreenWidthDp > o.SmallestScreenWidthDp
}
}
// screen size dp
if c.ScreenWidthDp != 0 || c.ScreenHeightDp != 0 || o.ScreenWidthDp != 0 || o.ScreenHeightDp != 0 {
myDelta := 0
otherDelta := 0
if r.ScreenWidthDp != 0 {
myDelta += int(r.ScreenWidthDp) - int(c.ScreenWidthDp)
otherDelta += int(r.ScreenWidthDp) - int(o.ScreenWidthDp)
}
if r.ScreenHeightDp != 0 {
myDelta += int(r.ScreenHeightDp) - int(c.ScreenHeightDp)
otherDelta += int(r.ScreenHeightDp) - int(o.ScreenHeightDp)
}
if myDelta != otherDelta {
return myDelta < otherDelta
}
}
// screen layout
if c.ScreenLayout != 0 || o.ScreenLayout != 0 {
mySL := c.ScreenLayout & MaskScreenSize
oSL := o.ScreenLayout & MaskScreenSize
if (mySL^oSL) != 0 && (r.ScreenLayout&MaskScreenSize) != 0 {
fixedMySL := mySL
fixedOSL := oSL
if (r.ScreenLayout & MaskScreenSize) >= ScreenSizeNormal {
if fixedMySL == 0 {
fixedMySL = ScreenSizeNormal
}
if fixedOSL == 0 {
fixedOSL = ScreenSizeNormal
}
}
if fixedMySL == fixedOSL {
return mySL != 0
}
return fixedMySL > fixedOSL
}
if ((c.ScreenLayout^o.ScreenLayout)&MaskScreenLong) != 0 &&
(r.ScreenLayout&MaskScreenLong) != 0 {
return (c.ScreenLayout & MaskScreenLong) != 0
}
}
// orientation
if c.Orientation != o.Orientation && r.Orientation != 0 {
return c.Orientation != 0
}
// uimode
if c.UIMode != 0 || o.UIMode != 0 {
diff := c.UIMode ^ o.UIMode
if (diff&MaskUIModeType) != 0 && (r.UIMode&MaskUIModeType) != 0 {
return (c.UIMode & MaskUIModeType) != 0
}
if (diff&MaskUIModeNight) != 0 && (r.UIMode&MaskUIModeNight) != 0 {
return (c.UIMode & MaskUIModeNight) != 0
}
}
// screen type
if c.Density != o.Density {
h := int(c.Density)
if h == 0 {
h = 160
}
l := int(o.Density)
if l == 0 {
l = 160
}
blmBigger := true
if l > h {
h, l = l, h
blmBigger = false
}
reqValue := int(r.Density)
if reqValue == 0 {
reqValue = 160
}
if reqValue >= h {
return blmBigger
}
if l >= reqValue {
return !blmBigger
}
if (2*l-reqValue)*h > reqValue*reqValue {
return !blmBigger
}
return blmBigger
}
if c.Touchscreen != o.Touchscreen && r.Touchscreen != 0 {
return c.Touchscreen != 0
}
// input
if c.InputFlags != 0 || o.InputFlags != 0 {
myKeysHidden := c.InputFlags & MaskKeysHidden
oKeysHidden := o.InputFlags & MaskKeysHidden
reqKeysHidden := r.InputFlags & MaskKeysHidden
if myKeysHidden != oKeysHidden && reqKeysHidden != 0 {
switch {
case myKeysHidden == 0:
return false
case oKeysHidden == 0:
return true
case reqKeysHidden == myKeysHidden:
return true
case reqKeysHidden == oKeysHidden:
return false
}
}
myNavHidden := c.InputFlags & MaskNavHidden
oNavHidden := o.InputFlags & MaskNavHidden
reqNavHidden := r.InputFlags & MaskNavHidden
if myNavHidden != oNavHidden && reqNavHidden != 0 {
switch {
case myNavHidden == 0:
return false
case oNavHidden == 0:
return true
}
}
}
if c.Keyboard != o.Keyboard && r.Keyboard != 0 {
return c.Keyboard != 0
}
if c.Navigation != o.Navigation && r.Navigation != 0 {
return c.Navigation != 0
}
// screen size
if c.ScreenWidth != 0 || c.ScreenHeight != 0 || o.ScreenWidth != 0 || o.ScreenHeight != 0 {
myDelta := 0
otherDelta := 0
if r.ScreenWidth != 0 {
myDelta += int(r.ScreenWidth) - int(c.ScreenWidth)
otherDelta += int(r.ScreenWidth) - int(o.ScreenWidth)
}
if r.ScreenHeight != 0 {
myDelta += int(r.ScreenHeight) - int(c.ScreenHeight)
otherDelta += int(r.ScreenHeight) - int(o.ScreenHeight)
}
if myDelta != otherDelta {
return myDelta < otherDelta
}
}
// version
if c.SDKVersion != 0 || o.MinorVersion != 0 {
if c.SDKVersion != o.SDKVersion && r.SDKVersion != 0 {
return c.SDKVersion > o.SDKVersion
}
if c.MinorVersion != o.MinorVersion && r.MinorVersion != 0 {
return c.MinorVersion != 0
}
}
return false
}
// IsLocaleMoreSpecificThan a positive integer if this config is more specific than o,
// a negative integer if |o| is more specific
// and 0 if they're equally specific.
func (c *ResTableConfig) IsLocaleMoreSpecificThan(o *ResTableConfig) int {
if (c.Language != [2]uint8{} || c.Country != [2]uint8{}) || (o.Language != [2]uint8{} || o.Country != [2]uint8{}) {
if c.Language != o.Language {
if c.Language == [2]uint8{} {
return -1
}
if o.Language == [2]uint8{} {
return 1
}
}
if c.Country != o.Country {
if c.Country == [2]uint8{} {
return -1
}
if o.Country == [2]uint8{} {
return 1
}
}
}
return 0
}
// IsLocaleBetterThan returns true if c is a better locale match than o for the r configuration.
func (c *ResTableConfig) IsLocaleBetterThan(o *ResTableConfig, r *ResTableConfig) bool {
if r.Language == [2]uint8{} && r.Country == [2]uint8{} {
// The request doesn't have a locale, so no resource is better
// than the other.
return false
}
if c.Language == [2]uint8{} && c.Country == [2]uint8{} && o.Language == [2]uint8{} && o.Country == [2]uint8{} {
// The locales parts of both resources are empty, so no one is better
// than the other.
return false
}
if c.Language != o.Language {
// The languages of the two resources are not the same.
// the US English resource have traditionally lived for most apps.
if r.Language == [2]uint8{'e', 'n'} {
if r.Country == [2]uint8{'U', 'S'} {
if c.Language != [2]uint8{} {
return c.Country == [2]uint8{} || c.Country == [2]uint8{'U', 'S'}
}
return !(c.Country == [2]uint8{} || c.Country == [2]uint8{'U', 'S'})
}
}
return c.Language != [2]uint8{}
}
if c.Country != o.Country {
return c.Country != [2]uint8{}
}
return false
}
// Match returns true if c can be considered a match for the parameters in settings.
func (c *ResTableConfig) Match(settings *ResTableConfig) bool {
// nil ResTableConfig always matches.
if settings == nil {
return true
} else if c == nil {
return *settings == ResTableConfig{}
}
// match imsi
if settings.Mcc == 0 {
if c.Mcc != 0 {
return false
}
} else {
if c.Mcc != 0 && c.Mcc != settings.Mcc {
return false
}
}
if settings.Mnc == 0 {
if c.Mnc != 0 {
return false
}
} else {
if c.Mnc != 0 && c.Mnc != settings.Mnc {
return false
}
}
// match locale
if c.Language != [2]uint8{0, 0} {
// Don't consider country and variants when deciding matches.
// If two configs differ only in their country and variant,
// they can be weeded out in the isMoreSpecificThan test.
if c.Language != settings.Language {
return false
}
if c.Country != [2]uint8{0, 0} {
if c.Country != settings.Country {
return false
}
}
}
// screen layout
layoutDir := c.ScreenLayout & MaskLayoutDir
setLayoutDir := settings.ScreenLayout & MaskLayoutDir
if layoutDir != 0 && layoutDir != setLayoutDir {
return false
}
screenSize := c.ScreenLayout & MaskScreenSize
setScreenSize := settings.ScreenLayout & MaskScreenSize
if screenSize != 0 && screenSize > setScreenSize {
return false
}
screenLong := c.ScreenLayout & MaskScreenLong
setScreenLong := settings.ScreenLayout & MaskScreenLong
if screenLong != 0 && screenLong != setScreenLong {
return false
}
// ui mode
uiModeType := c.UIMode & MaskUIModeType
setUIModeType := settings.UIMode & MaskUIModeType
if uiModeType != 0 && uiModeType != setUIModeType {
return false
}
uiModeNight := c.UIMode & MaskUIModeNight
setUIModeNight := settings.UIMode & MaskUIModeNight
if uiModeNight != 0 && uiModeNight != setUIModeNight {
return false
}
// smallest screen width dp
if c.SmallestScreenWidthDp != 0 &&
c.SmallestScreenWidthDp > settings.SmallestScreenWidthDp {
return false
}
// screen size dp
if c.ScreenWidthDp != 0 &&
c.ScreenWidthDp > settings.ScreenWidthDp {
return false
}
if c.ScreenHeightDp != 0 &&
c.ScreenHeightDp > settings.ScreenHeightDp {
return false
}
// screen type
if c.Orientation != 0 && c.Orientation != settings.Orientation {
return false
}
if c.Touchscreen != 0 && c.Touchscreen != settings.Touchscreen {
return false
}
// input
if c.InputFlags != 0 {
myKeysHidden := c.InputFlags & MaskKeysHidden
oKeysHidden := settings.InputFlags & MaskKeysHidden
if myKeysHidden != 0 && myKeysHidden != oKeysHidden {
if myKeysHidden != KeysHiddenNo || oKeysHidden != KeysHiddenSoft {
return false
}
}
myNavHidden := c.InputFlags & MaskNavHidden
oNavHidden := settings.InputFlags & MaskNavHidden
if myNavHidden != 0 && myNavHidden != oNavHidden {
return false
}
}
if c.Keyboard != 0 && c.Keyboard != settings.Keyboard {
return false
}
if c.Navigation != 0 && c.Navigation != settings.Navigation {
return false
}
// screen size
if c.ScreenWidth != 0 &&
c.ScreenWidth > settings.ScreenWidth {
return false
}
if c.ScreenHeight != 0 &&
c.ScreenHeight > settings.ScreenHeight {
return false
}
// version
if settings.SDKVersion != 0 && c.SDKVersion != 0 &&
c.SDKVersion > settings.SDKVersion {
return false
}
if settings.MinorVersion != 0 && c.MinorVersion != 0 &&
c.MinorVersion != settings.MinorVersion {
return false
}
return true
}
// Locale returns the locale of the configuration.
func (c *ResTableConfig) Locale() string {
if c.Language[0] == 0 {
return ""
}
if c.Country[0] == 0 {
return fmt.Sprintf("%c%c", c.Language[0], c.Language[1])
}
return fmt.Sprintf("%c%c-%c%c", c.Language[0], c.Language[1], c.Country[0], c.Country[1])
}