├── .gitignore ├── LICENSE ├── README.md ├── adb ├── adb.go ├── connection.go ├── device.go ├── image │ └── main.go └── sync.go ├── apk ├── apk.go ├── example │ └── parse.go └── xml.go └── pidcat ├── log.go └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Bill Best 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | android 2 | ====== 3 | 4 | General purpose Golang android library 5 | 6 | -------------------------------------------------------------------------------- /adb/adb.go: -------------------------------------------------------------------------------- 1 | package adb 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "log" 7 | "os" 8 | "time" 9 | ) 10 | 11 | const ( 12 | dalvikWarning = "WARNING: linker: libdvm.so has text relocations. This is wasting memory and is a security risk. Please fix." 13 | ) 14 | 15 | type Adb struct { 16 | Dialer 17 | Method Transport 18 | } 19 | 20 | var ( 21 | Default = &Adb{Dialer{"localhost", 5037}, Any} 22 | ) 23 | 24 | func Connect(host string, port int) *Adb { 25 | return &Adb{Dialer{host, port}, Any} 26 | } 27 | 28 | func Devices() []byte { 29 | return Default.Devices() 30 | } 31 | 32 | func WaitFor(t Transporter) { 33 | for { 34 | conn, _ := t.Dial() 35 | err := t.Transport(conn) 36 | 37 | if err == nil { 38 | return 39 | } 40 | 41 | defer conn.Close() 42 | } 43 | } 44 | 45 | func Log(t Transporter, args ...string) chan []byte { 46 | out := make(chan []byte) 47 | 48 | go func(out chan []byte) { 49 | defer close(out) 50 | conn, err := t.Dial() 51 | 52 | if err != nil { 53 | fmt.Println(err) 54 | return 55 | } 56 | 57 | defer conn.Close() 58 | 59 | err = t.Transport(conn) 60 | if err != nil { 61 | fmt.Println("more than one device or emulator") 62 | os.Exit(2) 63 | } 64 | conn.Log(args...) 65 | 66 | for { 67 | line, _, err := conn.r.ReadLine() 68 | if err != nil { 69 | break 70 | } 71 | out <- line 72 | } 73 | }(out) 74 | return out 75 | } 76 | 77 | func Shell(t Transporter, args ...string) chan []byte { 78 | out := make(chan []byte) 79 | 80 | go func(out chan []byte) { 81 | defer close(out) 82 | conn, err := t.Dial() 83 | 84 | if err != nil { 85 | fmt.Println(err) 86 | return 87 | } 88 | 89 | defer conn.Close() 90 | 91 | err = t.Transport(conn) 92 | if err != nil { 93 | fmt.Println(err) 94 | fmt.Println("more than one device or emulator") 95 | os.Exit(2) 96 | } 97 | conn.Shell(args...) 98 | 99 | for { 100 | line, _, err := conn.r.ReadLine() 101 | line = bytes.Replace(line, []byte{'\r'}, []byte{}, 0) 102 | if err != nil { 103 | break 104 | } 105 | out <- line 106 | } 107 | }(out) 108 | return out 109 | } 110 | 111 | func ShellSync(t Transporter, args ...string) []byte { 112 | out := Shell(t, args...) 113 | output := make([]byte, 0) 114 | for line := range out { 115 | output = append(output, line...) 116 | output = append(output, '\n') 117 | } 118 | return output 119 | } 120 | 121 | func (a *Adb) Transport(conn *AdbConn) error { 122 | switch a.Method { 123 | case Usb: 124 | return conn.TransportUsb() 125 | case Emulator: 126 | return conn.TransportEmulator() 127 | default: 128 | return conn.TransportAny() 129 | } 130 | } 131 | 132 | func timeTrack(start time.Time, name string) { 133 | elapsed := time.Since(start) 134 | log.Printf("%s took %s", name, elapsed) 135 | } 136 | 137 | func Frame(t Transporter) []byte { 138 | conn, err := t.Dial() 139 | if err != nil { 140 | return []byte{} 141 | } 142 | defer conn.Close() 143 | err = t.Transport(conn) 144 | if err != nil { 145 | fmt.Println(err) 146 | fmt.Println("more than one device or emulator") 147 | os.Exit(2) 148 | } 149 | 150 | _, err = conn.WriteCmd("framebuffer:") 151 | 152 | if err != nil { 153 | panic(err) 154 | } 155 | 156 | version := conn.readUint32() 157 | fmt.Printf("Version, %d\n", version) 158 | depth := conn.readUint32() 159 | fmt.Printf("Depth, %d\n", depth) 160 | size := conn.readUint32() 161 | fmt.Println("Size, ", size) 162 | height := conn.readUint32() 163 | fmt.Println("height, ", height) 164 | width := conn.readUint32() 165 | fmt.Println("width, ", width) 166 | ro := conn.readUint32() 167 | fmt.Println("ro, ", ro) 168 | rl := conn.readUint32() 169 | fmt.Println("rl ", rl) 170 | bo := conn.readUint32() 171 | fmt.Println("bo, ", bo) 172 | bl := conn.readUint32() 173 | fmt.Println("bl ", bl) 174 | gro := conn.readUint32() 175 | fmt.Println("gro, ", gro) 176 | gl := conn.readUint32() 177 | fmt.Println("gl ", gl) 178 | ao := conn.readUint32() 179 | fmt.Println("ao, ", ao) 180 | al := conn.readUint32() 181 | fmt.Println("al ", al) 182 | 183 | var lines []byte 184 | for i := 0; i < 3; i++ { 185 | lines = conn.readImageBytes(size) 186 | conn.Write([]byte{1}) 187 | } 188 | return lines 189 | } 190 | 191 | func (conn *AdbConn) readImageBytes(size uint32) []byte { 192 | defer timeTrack(time.Now(), "readImageBytes") 193 | lines := make([]byte, size) 194 | w := bytes.NewBuffer(lines) 195 | total, _ := w.ReadFrom(conn) 196 | log.Println("PUlled ", total) 197 | 198 | return w.Bytes() 199 | } 200 | 201 | func (adb *Adb) Devices() []byte { 202 | conn, err := adb.Dial() 203 | if err != nil { 204 | return []byte{} 205 | } 206 | defer conn.Close() 207 | 208 | conn.WriteCmd("host:devices") 209 | size, _ := conn.readSize(4) 210 | 211 | lines := make([]byte, size) 212 | conn.Read(lines) 213 | 214 | return lines 215 | } 216 | 217 | func (adb *Adb) TrackDevices() chan []byte { 218 | out := make(chan []byte) 219 | go func() { 220 | defer close(out) 221 | 222 | conn, err := adb.Dial() 223 | 224 | if err != nil { 225 | return 226 | } 227 | 228 | defer conn.Close() 229 | 230 | conn.WriteCmd("host:track-devices") 231 | 232 | for { 233 | size, err := conn.readSize(4) 234 | if err != nil { 235 | break 236 | } 237 | 238 | lines := make([]byte, size) 239 | _, err = conn.Read(lines) 240 | if err != nil { 241 | break 242 | } 243 | out <- lines 244 | } 245 | }() 246 | return out 247 | } 248 | 249 | func (adb *Adb) FindDevice(serial string) Device { 250 | var dev Device 251 | devices := adb.FindDevices(serial) 252 | if len(devices) > 0 { 253 | dev = *devices[0] 254 | } 255 | return dev 256 | } 257 | 258 | func (adb *Adb) FindDevices(serial ...string) []*Device { 259 | filter := &DeviceFilter{} 260 | filter.Serials = serial 261 | filter.MaxSdk = LATEST 262 | return adb.ListDevices(filter) 263 | } 264 | 265 | func ListDevices(filter *DeviceFilter) []*Device { 266 | return Default.ListDevices(filter) 267 | } 268 | 269 | func (adb *Adb) ListDevices(filter *DeviceFilter) []*Device { 270 | output := adb.Devices() 271 | return adb.ParseDevices(filter, output) 272 | } 273 | -------------------------------------------------------------------------------- /adb/connection.go: -------------------------------------------------------------------------------- 1 | package adb 2 | 3 | import ( 4 | "bufio" 5 | "encoding/binary" 6 | "errors" 7 | "fmt" 8 | "net" 9 | "strconv" 10 | "strings" 11 | ) 12 | 13 | type Transport int 14 | 15 | const ( 16 | Any Transport = iota 17 | Emulator 18 | Usb 19 | ) 20 | 21 | type Transporter interface { 22 | Dial() (*AdbConn, error) 23 | Transport(conn *AdbConn) error 24 | } 25 | 26 | type Dialer struct { 27 | Host string 28 | Port int 29 | } 30 | 31 | type AdbConn struct { 32 | conn net.Conn 33 | r *bufio.Reader 34 | } 35 | 36 | func (a *Dialer) Dial() (*AdbConn, error) { 37 | h := fmt.Sprintf("%s:%d", a.Host, a.Port) 38 | c, err := net.Dial("tcp", h) 39 | if err != nil { 40 | if c != nil { 41 | fmt.Printf("Closing connection to %s\n", c) 42 | c.Close() 43 | } 44 | return nil, err 45 | } 46 | return &AdbConn{c, bufio.NewReader(c)}, nil 47 | } 48 | 49 | func (a *AdbConn) TransportAny() error { 50 | cmd := fmt.Sprintf("host:transport-any") 51 | _, err := a.WriteCmd(cmd) 52 | return err 53 | } 54 | 55 | func (a *AdbConn) TransportEmulator() error { 56 | cmd := fmt.Sprintf("host:transport-local") 57 | _, err := a.WriteCmd(cmd) 58 | return err 59 | } 60 | 61 | func (a *AdbConn) TransportUsb() error { 62 | cmd := fmt.Sprintf("host:transport-usb") 63 | _, err := a.WriteCmd(cmd) 64 | return err 65 | } 66 | 67 | func (a *AdbConn) TransportSerial(ser string) error { 68 | cmd := fmt.Sprintf("host:transport:%s", ser) 69 | _, err := a.WriteCmd(cmd) 70 | return err 71 | } 72 | 73 | func (a *AdbConn) Shell(args ...string) error { 74 | cmd := fmt.Sprintf("shell:%s", strings.Join(args, " ")) 75 | _, err := a.WriteCmd(cmd) 76 | return err 77 | } 78 | 79 | func (a *AdbConn) Log(args ...string) error { 80 | cmd := fmt.Sprintf("log:%s", strings.Join(args, " ")) 81 | _, err := a.WriteCmd(cmd) 82 | return err 83 | } 84 | 85 | func (a *AdbConn) readSize(bcount int) (uint64, error) { 86 | size := make([]byte, bcount) 87 | a.r.Read(size) 88 | return strconv.ParseUint(string(size), 16, 0) 89 | } 90 | 91 | func (a *AdbConn) WriteCmd(cmd string) (int, error) { 92 | prefix := fmt.Sprintf("%04x", len(cmd)) 93 | w := bufio.NewWriter(a) 94 | w.WriteString(prefix) 95 | i, err := w.WriteString(cmd) 96 | 97 | if err != nil { 98 | return 0, errors.New(`Could not write to ADB server`) 99 | } 100 | 101 | w.Flush() 102 | 103 | return i, a.VerifyOk() 104 | } 105 | 106 | func (a *AdbConn) readUint32() uint32 { 107 | size := make([]byte, 4) 108 | a.r.Read(size) 109 | return binary.LittleEndian.Uint32(size) 110 | } 111 | 112 | func (a *AdbConn) ReadCode() (string, error) { 113 | status := make([]byte, 4) 114 | _, err := a.Read(status) 115 | if err != nil { 116 | return "UNKN", err 117 | } 118 | return string(status), nil 119 | } 120 | 121 | func (a *AdbConn) VerifyOk() error { 122 | code, err := a.ReadCode() 123 | if err != nil || code != `OKAY` { 124 | return errors.New(`Invalid connection CODE: ` + code) 125 | } 126 | return nil 127 | } 128 | 129 | func (a *AdbConn) Write(b []byte) (int, error) { 130 | if a == nil || a.conn == nil { 131 | return 0, errors.New(`Could not write to ADB server`) 132 | } 133 | return a.conn.Write(b) 134 | } 135 | 136 | func (a *AdbConn) Read(p []byte) (int, error) { 137 | if a == nil { 138 | return 0, errors.New(`Could not read from ADB server`) 139 | } else if a.r == nil { 140 | a.r = bufio.NewReader(a.conn) 141 | } 142 | 143 | return a.r.Read(p) 144 | } 145 | 146 | func (a *AdbConn) Close() error { 147 | if a.conn != nil { 148 | return a.conn.Close() 149 | } 150 | return nil 151 | } 152 | -------------------------------------------------------------------------------- /adb/device.go: -------------------------------------------------------------------------------- 1 | package adb 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "math" 7 | "regexp" 8 | "strconv" 9 | "strings" 10 | "sync" 11 | ) 12 | 13 | type DensityBucket int 14 | type DeviceType int 15 | type SdkVersion int 16 | 17 | const ( 18 | PHONE DeviceType = iota 19 | TABLET_7 20 | TABLET_10 21 | ) 22 | 23 | const ( 24 | LDPI DensityBucket = 120 25 | MDPI = 160 26 | HDPI = 240 27 | XHDPI = 320 28 | XXHDPI = 480 29 | XXXHDPI = 640 30 | ) 31 | 32 | const ( 33 | ECLAIR SdkVersion = iota + 7 34 | FROYO 35 | GINGERBREAD 36 | GINGERBREAD_MR1 37 | HONEYCOMB 38 | HONEYCOMB_MR1 39 | HONEYCOMB_MR2 40 | ICE_CREAM_SANDWICH 41 | ICE_CREAM_SANDWICH_MR1 42 | JELLY_BEAN 43 | JELLY_BEAN_MR1 44 | JELLY_BEAN_MR2 45 | KITKAT 46 | WEAR 47 | LOLLIPOP 48 | LATEST = LOLLIPOP 49 | ) 50 | 51 | var typeMap = map[DeviceType]string{ 52 | PHONE: `Phone`, 53 | TABLET_7: `7in Tablet`, 54 | TABLET_10: `10in Tablet`, 55 | } 56 | 57 | var sdkMap = map[SdkVersion]string{ 58 | ECLAIR: `ECLAIR`, 59 | FROYO: `FROYO`, 60 | GINGERBREAD: `GINGERBREAD`, 61 | GINGERBREAD_MR1: `GINGERBREAD_MR1`, 62 | HONEYCOMB: `HONEYCOMB`, 63 | HONEYCOMB_MR1: `HONEYCOMB_MR1`, 64 | HONEYCOMB_MR2: `HONEYCOMB_MR2`, 65 | ICE_CREAM_SANDWICH: `ICE_CREAM_SANDWICH`, 66 | ICE_CREAM_SANDWICH_MR1: `ICE_CREAM_SANDWICH_MR1`, 67 | JELLY_BEAN: `JELLY_BEAN`, 68 | JELLY_BEAN_MR1: `JELLY_BEAN_MR1`, 69 | JELLY_BEAN_MR2: `JELLY_BEAN_MR2`, 70 | KITKAT: `KITKAT`, 71 | WEAR: `WEAR v1`, 72 | LOLLIPOP: `LOLLIPOP`, 73 | } 74 | 75 | type Device struct { 76 | Dialer `json:"-"` 77 | Serial string `json:"serial"` 78 | Manufacturer string `json:"manufacturer"` 79 | Model string `json:"model"` 80 | Sdk SdkVersion `json:"sdk"` 81 | Version string `json:"version"` 82 | Density DensityBucket `json:"density"` 83 | Height int64 `json:"height"` 84 | Width int64 `json:"width"` 85 | Properties map[string]string `json:_` 86 | } 87 | 88 | type DeviceFilter struct { 89 | Type DeviceType 90 | Serials []string 91 | Density DensityBucket 92 | MinSdk SdkVersion 93 | MaxSdk SdkVersion 94 | } 95 | 96 | var ( 97 | AllDevices = &DeviceFilter{MaxSdk: LATEST} 98 | ) 99 | 100 | func (s SdkVersion) String() string { 101 | return sdkMap[s] 102 | } 103 | 104 | // filter -f "serials=[...];type=tablet;count=5;version >= 4.1.1;" 105 | 106 | /*func GetFilter(arg string) {*/ 107 | 108 | /*}*/ 109 | 110 | type DeviceWatcher []chan []*Device 111 | 112 | func (d *Device) Type() DeviceType { 113 | sw := math.Min(float64(d.Height), float64(d.Width)) 114 | dip := float64(LDPI) / float64(d.Density) * sw 115 | if dip >= 720 { 116 | return TABLET_10 117 | } else if dip >= 600 { 118 | return TABLET_7 119 | } 120 | return PHONE 121 | } 122 | 123 | func (d *Device) Transport(conn *AdbConn) error { 124 | return conn.TransportSerial(d.Serial) 125 | } 126 | 127 | func (adb *Adb) ParseDevices(filter *DeviceFilter, input []byte) []*Device { 128 | lines := strings.Split(string(input), "\n") 129 | 130 | devices := make([]*Device, 0, len(lines)) 131 | 132 | var wg sync.WaitGroup 133 | 134 | for _, line := range lines { 135 | if strings.Contains(line, "device") && strings.TrimSpace(line) != "" { 136 | device := strings.Split(line, "\t")[0] 137 | 138 | d := &Device{Dialer: adb.Dialer, Serial: device} 139 | devices = append(devices, d) 140 | 141 | wg.Add(1) 142 | go func() { 143 | defer wg.Done() 144 | d.Update() 145 | }() 146 | } 147 | } 148 | 149 | wg.Wait() 150 | 151 | result := make([]*Device, 0, len(lines)) 152 | for _, device := range devices { 153 | if device.MatchFilter(filter) { 154 | result = append(result, device) 155 | } 156 | } 157 | return result 158 | } 159 | 160 | func stringInSlice(a string, list []string) bool { 161 | for _, b := range list { 162 | if b == a { 163 | return true 164 | } 165 | } 166 | return len(list) == 0 167 | } 168 | 169 | func (d *Device) MatchFilter(filter *DeviceFilter) bool { 170 | if filter == nil { 171 | return true 172 | } 173 | 174 | if d.Sdk < filter.MinSdk { 175 | return false 176 | } else if filter.MaxSdk != 0 && d.Sdk > filter.MaxSdk { 177 | return false 178 | } else if !stringInSlice(d.Serial, filter.Serials) { 179 | return false 180 | } else if filter.Density != 0 && filter.Density != d.Density { 181 | return false 182 | } 183 | return true 184 | } 185 | 186 | func (d *Device) RefreshProps() { 187 | d.Properties = make(map[string]string) 188 | 189 | proprx, err := regexp.Compile("\\[(.*)\\]: \\[(.*)\\]") 190 | 191 | if err != nil { 192 | panic(err) 193 | } 194 | 195 | out := Shell(d, "getprop") 196 | for line := range out { 197 | if line != nil { 198 | matches := proprx.FindSubmatch(line) 199 | if len(matches) > 2 { 200 | d.Properties[string(matches[1])] = string(matches[2]) 201 | } 202 | } 203 | } 204 | } 205 | 206 | func (d *Device) GetProp(prop string) string { 207 | return d.Properties[prop] 208 | } 209 | 210 | func (d *Device) HasPackage(pack string) bool { 211 | return d.findValue(pack, "pm", "list", "packages", "-3") 212 | } 213 | 214 | func (d *Device) SetScreenOn(on bool) { 215 | current := d.findValue("mScreenOn=false", "dumpsys", "input_method") 216 | if current && on || !current && !on { 217 | d.SendKey(26) 218 | } 219 | } 220 | 221 | func (d *Device) findValue(val string, args ...string) bool { 222 | out := Shell(d, args...) 223 | current := false 224 | for line := range out { 225 | if line != nil { 226 | current = bytes.Contains(line, []byte(val)) 227 | if current { 228 | break 229 | } 230 | } 231 | } 232 | return current 233 | } 234 | 235 | func (d *Device) SendKey(aKey int) { 236 | ShellSync(d, "input", "keyevent", fmt.Sprintf("%d", aKey)) 237 | } 238 | 239 | func (d *Device) Unlock() { 240 | current := d.findValue("mLockScreenShown true", "dumpsys", "activity") 241 | if current { 242 | d.SendKey(82) 243 | } 244 | } 245 | 246 | func (d *Device) Update() { 247 | 248 | WaitFor(d) 249 | d.RefreshProps() 250 | 251 | out := []string{ 252 | d.GetProp("ro.product.manufacturer"), 253 | d.GetProp("ro.product.model"), 254 | d.GetProp("ro.build.version.release"), 255 | d.GetProp("ro.build.version.sdk"), 256 | d.GetProp("ro.sf.lcd_density"), 257 | } 258 | 259 | d.Manufacturer = out[0] 260 | d.Model = out[1] 261 | d.Version = out[2] 262 | 263 | // Parse Version Code 264 | sdk_int, _ := strconv.ParseInt(out[3], 10, 0) 265 | d.Sdk = SdkVersion(sdk_int) 266 | 267 | // Parse DensityBucket 268 | density, _ := strconv.ParseInt(out[4], 10, 0) 269 | d.Density = DensityBucket(density) 270 | } 271 | 272 | func (d *Device) String() string { 273 | return fmt.Sprintf("%s\t%s %s\t[%s (%s) %s ]", d.Serial, d.Manufacturer, d.Model, d.Version, sdkMap[d.Sdk], typeMap[d.Type()]) 274 | } 275 | -------------------------------------------------------------------------------- /adb/image/main.go: -------------------------------------------------------------------------------- 1 | package image 2 | -------------------------------------------------------------------------------- /adb/sync.go: -------------------------------------------------------------------------------- 1 | package adb 2 | 3 | import ( 4 | "bufio" 5 | "encoding/binary" 6 | "errors" 7 | "fmt" 8 | "io" 9 | "log" 10 | "os" 11 | ) 12 | 13 | func readUInt32(a *AdbConn) uint32 { 14 | b := make([]byte, 4) 15 | a.Read(b) 16 | return binary.LittleEndian.Uint32(b) 17 | } 18 | 19 | func parseDent(a *AdbConn) { 20 | readUInt32(a) // MODE 21 | readUInt32(a) // SIZE 22 | readUInt32(a) // MODIFIED TIME 23 | length := readUInt32(a) // NAME LENGTH 24 | 25 | b := make([]byte, length) 26 | a.Read(b) 27 | 28 | log.Printf("%s\n", b) 29 | } 30 | 31 | func Ls(t Transporter, remote string) ([]byte, error) { 32 | conn, err := t.Dial() 33 | if err != nil { 34 | return []byte{}, err 35 | } 36 | defer conn.Close() 37 | 38 | t.Transport(conn) 39 | _, err = conn.WriteCmd("sync:") 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | w := bufio.NewWriter(conn) 45 | w.WriteString("LIST") 46 | binary.Write(w, binary.LittleEndian, uint32(len(remote))) 47 | w.WriteString(remote) 48 | w.Flush() 49 | 50 | b := make([]byte, 4) 51 | for { 52 | conn.Read(b) 53 | id := string(b) 54 | if id == "DENT" { 55 | parseDent(conn) 56 | } else if id == "DONE" { 57 | break 58 | } 59 | } 60 | 61 | return b, nil 62 | } 63 | 64 | func PushToDevices(devices []*Device, local io.Reader, mode os.FileMode, modtime uint32, remote string) error { 65 | d := make([]Transporter, 0, len(devices)) 66 | for _, t := range devices { 67 | d = append(d, Transporter(t)) 68 | } 69 | 70 | return Push(d, local, mode, modtime, remote) 71 | } 72 | 73 | func Push(devices []Transporter, local io.Reader, mode os.FileMode, modtime uint32, remote string) error { 74 | d := make([]io.Writer, 0, len(devices)) 75 | for _, t := range devices { 76 | conn, err := GetPushWriter(t, remote, uint32(mode)) 77 | if err != nil { 78 | return err 79 | } 80 | d = append(d, io.Writer(conn)) 81 | defer conn.VerifyOk() 82 | } 83 | 84 | reader := bufio.NewReader(local) 85 | sections := NewSectionedMultiWriter(d...) 86 | writer := bufio.NewWriter(sections) 87 | writer.ReadFrom(reader) 88 | writer.Flush() 89 | sections.Close() 90 | 91 | wr := bufio.NewWriter(io.MultiWriter(d...)) 92 | wr.WriteString("DONE") 93 | binary.Write(wr, binary.LittleEndian, modtime) 94 | wr.Flush() 95 | 96 | return nil 97 | } 98 | 99 | func PushFile(t []Transporter, local *os.File, remote string) error { 100 | info, err := local.Stat() 101 | if err != nil { 102 | return err 103 | } 104 | 105 | return Push(t, local, info.Mode(), uint32(info.ModTime().Unix()), remote) 106 | } 107 | 108 | func PushFileToDevices(devices []*Device, local *os.File, remote string) error { 109 | d := make([]Transporter, 0, len(devices)) 110 | for _, t := range devices { 111 | d = append(d, Transporter(t)) 112 | } 113 | 114 | return PushFile(d, local, remote) 115 | } 116 | 117 | func PushFileTo(t Transporter, local *os.File, remote string) error { 118 | return PushFile([]Transporter{t}, local, remote) 119 | } 120 | 121 | func GetPushWriter(t Transporter, remote string, filePerm uint32) (*AdbConn, error) { 122 | conn, err := t.Dial() 123 | if err != nil { 124 | return nil, err 125 | } 126 | 127 | t.Transport(conn) 128 | _, err = conn.WriteCmd("sync:") 129 | if err != nil { 130 | return nil, err 131 | } 132 | 133 | w := bufio.NewWriter(conn) 134 | w.WriteString("SEND") 135 | binary.Write(w, binary.LittleEndian, uint32(len(remote)+5)) 136 | w.WriteString(remote) 137 | w.WriteString(",") 138 | binary.Write(w, binary.LittleEndian, filePerm) 139 | w.Flush() 140 | 141 | return conn, nil 142 | } 143 | 144 | func Pull(t Transporter, local io.Writer, remote string) error { 145 | conn, err := t.Dial() 146 | if err != nil { 147 | return err 148 | } 149 | 150 | t.Transport(conn) 151 | _, err = conn.WriteCmd("sync:") 152 | if err != nil { 153 | return err 154 | } 155 | 156 | w := bufio.NewWriter(conn) 157 | w.WriteString("RECV") 158 | binary.Write(w, binary.LittleEndian, uint32(len(remote))) 159 | w.WriteString(remote) 160 | w.Flush() 161 | 162 | writer := bufio.NewWriter(local) 163 | code, err := conn.ReadCode() 164 | 165 | if code == `FAIL` { 166 | return errors.New(fmt.Sprintf("Unable to locate file %s", remote)) 167 | } 168 | 169 | var n uint32 170 | for err == nil && code != `DONE` { 171 | binary.Read(conn, binary.LittleEndian, &n) 172 | writer.ReadFrom(&io.LimitedReader{conn, int64(n)}) 173 | writer.Flush() 174 | 175 | code, err = conn.ReadCode() 176 | } 177 | 178 | return nil 179 | } 180 | 181 | type SectionedMultiWriter struct { 182 | writer io.Writer 183 | buffer []byte 184 | bufferIdx int 185 | section int 186 | } 187 | 188 | func NewSectionedMultiWriter(writers ...io.Writer) *SectionedMultiWriter { 189 | return &SectionedMultiWriter{writer: io.MultiWriter(writers...), buffer: make([]byte, 65536)} 190 | } 191 | 192 | func (w *SectionedMultiWriter) Write(b []byte) (int, error) { 193 | i := copy(w.buffer[w.bufferIdx:], b) 194 | w.bufferIdx += i 195 | 196 | atmax := w.bufferIdx == 65536 197 | if i < len(b) || atmax { 198 | w.section++ 199 | w.Flush() 200 | 201 | if !atmax { 202 | return w.Write(b[i:]) 203 | } 204 | } 205 | return i, nil 206 | } 207 | 208 | func (w *SectionedMultiWriter) Flush() { 209 | wr := bufio.NewWriter(w.writer) 210 | wr.WriteString("DATA") 211 | binary.Write(wr, binary.LittleEndian, uint32(w.bufferIdx)) 212 | wr.Write(w.buffer[:w.bufferIdx]) 213 | wr.Flush() 214 | 215 | w.bufferIdx = 0 216 | } 217 | 218 | func (w *SectionedMultiWriter) Close() error { 219 | if len(w.buffer) != 0 { 220 | w.Flush() 221 | } 222 | return nil 223 | } 224 | -------------------------------------------------------------------------------- /apk/apk.go: -------------------------------------------------------------------------------- 1 | package apk 2 | 3 | import "github.com/wmbest2/android/adb" 4 | 5 | type Instrumentation struct { 6 | Name string `xml:"name,attr"` 7 | Target string `xml:"targetPackage,attr"` 8 | HandleProfiling bool `xml:"handleProfiling,attr"` 9 | FunctionalTest bool `xml:"functionalTest,attr"` 10 | } 11 | 12 | type ActivityAction struct { 13 | Name string `xml:"name,attr"` 14 | } 15 | 16 | type ActivityCategory struct { 17 | Name string `xml:"name,attr"` 18 | } 19 | 20 | type ActivityIntentFilter struct { 21 | Action ActivityAction `xml:"action"` 22 | Category ActivityCategory `xml:"category"` 23 | } 24 | 25 | type AppActivity struct { 26 | Theme string `xml:"theme,attr"` 27 | Name string `xml:"name,attr"` 28 | Label string `xml:"label,attr"` 29 | IntentFilter []ActivityIntentFilter `xml:"intent-filter"` 30 | } 31 | 32 | type Application struct { 33 | AllowTaskReparenting bool `xml:"allowTaskReparenting,attr"` 34 | AllowBackup bool `xml:"allowBackup,attr"` 35 | BackupAgent string `xml:"backupAgent,attr"` 36 | Debuggable bool `xml:"debuggable,attr"` 37 | Description string `xml:"description,attr"` 38 | Enabled bool `xml:"enabled,attr"` 39 | HasCode bool `xml:"hasCode,attr"` 40 | HardwareAccelerated bool `xml:"hardwareAccelerated,attr"` 41 | Icon string `xml:"icon,attr"` 42 | KillAfterRestore bool `xml:"killAfterRestore,attr"` 43 | LargeHeap bool `xml:"largeHeap,attr"` 44 | Label string `xml:"label,attr"` 45 | Logo int `xml:"logo,attr"` 46 | ManageSpaceActivity string `xml:"manageSpaceActivity,attr"` 47 | Name string `xml:"name,attr"` 48 | Permission string `xml:"permission,attr"` 49 | Persistent bool `xml:"persistent,attr"` 50 | Process string `xml:"process,attr"` 51 | RestoreAnyVersion bool `xml:"restoreAnyVersion,attr"` 52 | RequiredAccountType string `xml:"requiredAccountType,attr"` 53 | RestrictedAccountType string `xml:"restrictedAccountType,attr"` 54 | SupportsRtl bool `xml:"supportsRtl,attr"` 55 | TaskAffinity string `xml:"taskAffinity,attr"` 56 | TestOnly bool `xml:"testOnly,attr"` 57 | Theme int `xml:"theme,attr"` 58 | UiOptions string `xml:"uiOptions,attr"` 59 | VmSafeMode bool `xml:"vmSafeMode,attr"` 60 | Activity []AppActivity `xml:"activity"` 61 | } 62 | 63 | type UsesSdk struct { 64 | Min adb.SdkVersion `xml:"minSdkVersion,attr"` 65 | Target adb.SdkVersion `xml:"targetSdkVersion,attr"` 66 | Max adb.SdkVersion `xml:"maxSdkVersion,attr"` 67 | } 68 | 69 | type Manifest struct { 70 | Package string `xml:"package,attr"` 71 | VersionCode int `xml:"versionCode,attr"` 72 | VersionName string `xml:"versionName,attr"` 73 | App Application `xml:"application"` 74 | Instrument Instrumentation `xml:"instrumentation"` 75 | Sdk UsesSdk `xml:"uses-sdk"` 76 | } 77 | -------------------------------------------------------------------------------- /apk/example/parse.go: -------------------------------------------------------------------------------- 1 | // Parse out 2 | // package and activity(MAIN) from apk 3 | package main 4 | 5 | import ( 6 | "archive/zip" 7 | "flag" 8 | "fmt" 9 | "io/ioutil" 10 | "log" 11 | 12 | "github.com/nick-fedesna/android/apk" 13 | ) 14 | 15 | func parseManifest(data []byte) { 16 | var manifest apk.Manifest 17 | err := apk.Unmarshal(data, &manifest) 18 | if err != nil { 19 | log.Fatal(err) 20 | } 21 | var launchActivity apk.AppActivity 22 | for _, act := range manifest.App.Activity { 23 | for _, intent := range act.IntentFilter { 24 | if intent.Action.Name == "android.intent.action.MAIN" && 25 | intent.Category.Name == "android.intent.category.LAUNCHER" { 26 | launchActivity = act 27 | goto FOUND 28 | } 29 | } 30 | } 31 | FOUND: 32 | fmt.Println(manifest.Package) 33 | fmt.Println(launchActivity.Name) 34 | fmt.Printf("adb shell am start -n %s/%s\n", manifest.Package, launchActivity.Name) 35 | //out, _ := xml.MarshalIndent(manifest, "", "\t") 36 | //fmt.Printf("%s\n", string(out)) 37 | } 38 | 39 | func ReadManifestFromApk(filename string) (data []byte, err error) { 40 | r, err := zip.OpenReader(filename) 41 | if err != nil { 42 | return 43 | } 44 | defer r.Close() 45 | for _, f := range r.File { 46 | if f.Name != "AndroidManifest.xml" { 47 | continue 48 | } 49 | rc, er := f.Open() 50 | if er != nil { 51 | return nil, er 52 | } 53 | data, err = ioutil.ReadAll(rc) 54 | rc.Close() 55 | return 56 | } 57 | return nil, fmt.Errorf("File not found: AndroidManifest.xml") 58 | } 59 | 60 | func main() { 61 | flag.Parse() 62 | data, err := ReadManifestFromApk(flag.Arg(0)) 63 | if err != nil { 64 | log.Fatal(err) 65 | } 66 | parseManifest(data) 67 | } 68 | -------------------------------------------------------------------------------- /apk/xml.go: -------------------------------------------------------------------------------- 1 | package apk 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "encoding/xml" 7 | "errors" 8 | "fmt" 9 | "io" 10 | "math" 11 | ) 12 | 13 | const ( 14 | CHUNK_AXML_FILE = 0x00080003 15 | CHUNK_RESOURCEIDS = 0x00080180 16 | CHUNK_STRINGS = 0x001C0001 17 | CHUNK_XML_END_NAMESPACE = 0x00100101 18 | CHUNK_XML_END_TAG = 0x00100103 19 | CHUNK_XML_START_NAMESPACE = 0x00100100 20 | CHUNK_XML_START_TAG = 0x00100102 21 | CHUNK_XML_TEXT = 0x00100104 22 | UTF8_FLAG = 0x00000100 23 | SKIP_BLOCK = 0xFFFFFFFF 24 | ) 25 | 26 | type stringsMeta struct { 27 | Nstrings uint32 28 | StyleOffsetCount uint32 29 | Flags uint32 30 | StringDataOffset uint32 31 | Stylesoffset uint32 32 | DataOffset []uint32 33 | } 34 | 35 | // decompressXML -- Parse the 'compressed' binary form of Android XML docs 36 | // such as for AndroidManifest.xml in .apk files 37 | func Unmarshal(data []byte, v interface{}) error { 38 | 39 | body := bytes.NewReader(data) 40 | 41 | var blocktype, size, indent, header uint32 42 | var stringsData stringsMeta 43 | 44 | // Check Header 45 | binary.Read(body, binary.LittleEndian, &header) 46 | if header != CHUNK_AXML_FILE { 47 | return errors.New("AXML file has wrong header") 48 | } 49 | 50 | // Check filesize 51 | binary.Read(body, binary.LittleEndian, &header) 52 | if int(header) != len(data) { 53 | return errors.New("AXML file has the wrong size") 54 | } 55 | 56 | var output string 57 | // Start offset at 8 bytes for header and size 58 | for offset := uint32(8); offset < header; { 59 | var lineNumber, skip, nsIdx, nameIdx, flag uint32 60 | binary.Read(body, binary.LittleEndian, &blocktype) 61 | binary.Read(body, binary.LittleEndian, &size) 62 | if blocktype != CHUNK_RESOURCEIDS && blocktype != CHUNK_STRINGS { 63 | binary.Read(body, binary.LittleEndian, &lineNumber) 64 | binary.Read(body, binary.LittleEndian, &skip) 65 | if skip != SKIP_BLOCK { 66 | return errors.New("Error: Expected block 0xFFFFFFFF") 67 | } 68 | binary.Read(body, binary.LittleEndian, &nsIdx) 69 | binary.Read(body, binary.LittleEndian, &nameIdx) 70 | binary.Read(body, binary.LittleEndian, &flag) 71 | } 72 | switch blocktype { 73 | default: 74 | return fmt.Errorf("Unkown chunk type: %X", blocktype) 75 | case CHUNK_RESOURCEIDS: 76 | case CHUNK_STRINGS: 77 | /* +------------------------------------+ 78 | * | Nstrings uint32 | 79 | * | StyleOffsetCount uint32 | 80 | * | Flags uint32 | 81 | * | StringDataOffset uint32 | 82 | * | flag uint32 | 83 | * | Stylesoffset uint32 | 84 | * +------------------------------------+ 85 | * | +--------------------------------+ | 86 | * | | DataOffset uint32 | | 87 | * | +--------------------------------+ | 88 | * | Repeat Nstrings times | 89 | * +------------------------------------+ 90 | * | 91 | * +------------------------------------+ 92 | */ 93 | binary.Read(body, binary.LittleEndian, &stringsData.Nstrings) 94 | binary.Read(body, binary.LittleEndian, &stringsData.StyleOffsetCount) 95 | binary.Read(body, binary.LittleEndian, &stringsData.Flags) 96 | binary.Read(body, binary.LittleEndian, &stringsData.StringDataOffset) 97 | binary.Read(body, binary.LittleEndian, &stringsData.Stylesoffset) 98 | 99 | for i := uint32(0); i < stringsData.Nstrings; i++ { 100 | var offset uint32 101 | binary.Read(body, binary.LittleEndian, &offset) 102 | stringsData.DataOffset = append(stringsData.DataOffset, offset) 103 | } 104 | stringsData.StringDataOffset = 0x24 + stringsData.Nstrings*4 105 | case CHUNK_XML_END_NAMESPACE: 106 | case CHUNK_XML_END_TAG: 107 | indent-- 108 | name := compXmlStringAt(body, stringsData, nameIdx) 109 | output = fmt.Sprintf("%s%s\n", output, computeIndent(indent), name) 110 | case CHUNK_XML_START_NAMESPACE: 111 | case CHUNK_XML_START_TAG: 112 | /* +----------------------------- w-------+ 113 | * | lineNumber uint32 | 114 | * | skip uint32 = SKIP_BLOCK | 115 | * | nsIdx uint32 | 116 | * | nameIdx uint32 | 117 | * | flag uint32 = 0x00140014 | 118 | * | attributeCount uint16 | 119 | * +------------------------------------+ 120 | * | +--------------------------------+ | 121 | * | | nsIdx uint32 | | 122 | * | | nameIdx uint32 | | 123 | * | | valueString uint32 // Skipped | | 124 | * | | aValueType uint32 | | 125 | * | | aValue uint32 | | 126 | * | +--------------------------------+ | 127 | * | Repeat attributeCount times | 128 | * +------------------------------------+ 129 | */ 130 | 131 | var attributeCount, junk uint32 132 | // Check if flag is magick number 133 | // https://code.google.com/p/axml/source/browse/src/main/java/pxb/android/axml/AxmlReader.java?r=9bc9e64ef832736a93750998a9fa1d4406b858c3#102 134 | if flag != 0x00140014 { 135 | return fmt.Errorf("Expected flag 0x00140014, found %08X at %08X\n", flag, offset+4*6) 136 | } 137 | 138 | name := compXmlStringAt(body, stringsData, nameIdx) 139 | 140 | binary.Read(body, binary.LittleEndian, &attributeCount) 141 | binary.Read(body, binary.LittleEndian, &junk) 142 | 143 | var att string 144 | // Look for the Attributes 145 | for i := 0; i < int(attributeCount); i++ { 146 | var attrNameSi, attrNSSi, attrValueSi, flags, attrResId uint32 147 | binary.Read(body, binary.LittleEndian, &attrNSSi) 148 | binary.Read(body, binary.LittleEndian, &attrNameSi) 149 | binary.Read(body, binary.LittleEndian, &attrValueSi) 150 | binary.Read(body, binary.LittleEndian, &flags) 151 | binary.Read(body, binary.LittleEndian, &attrResId) 152 | 153 | attrName := compXmlStringAt(body, stringsData, attrNameSi) 154 | 155 | var attrValue string 156 | if attrValueSi != 0xffffffff { 157 | attrValue = compXmlStringAt(body, stringsData, attrValueSi) 158 | } else { 159 | attrValue = fmt.Sprintf("%d", attrResId) 160 | } 161 | att = fmt.Sprintf("%s %s=\"%s\"", att, attrName, attrValue) 162 | } 163 | 164 | output = fmt.Sprintf("%s%s<%s%s>\n", output, computeIndent(indent), name, att) 165 | indent++ 166 | case CHUNK_XML_TEXT: 167 | } 168 | offset += size 169 | body.Seek(int64(offset), 0) 170 | } 171 | return xml.Unmarshal([]byte(output), v) 172 | } 173 | 174 | func computeIndent(indent uint32) string { 175 | spaces := string(" ") 176 | m := int(math.Min(float64(indent*2), float64(len(spaces)))) 177 | return spaces[:m] 178 | } 179 | 180 | // compXmlStringAt -- Return the string stored in StringTable format at 181 | // offset strOff. This offset points to the 16 bit string length, which 182 | // is followed by that number of 16 bit (Unicode) chars. 183 | func compXmlStringAt(arr io.ReaderAt, meta stringsMeta, strOff uint32) string { 184 | if strOff == 0xffffffff { 185 | return "" 186 | } 187 | length := make([]byte, 2) 188 | off := meta.StringDataOffset + meta.DataOffset[strOff] 189 | arr.ReadAt(length, int64(off)) 190 | strLen := int(length[1]<<8 + length[0]) 191 | 192 | chars := make([]byte, int64(strLen)) 193 | ii := 0 194 | for i := 0; i < strLen; i++ { 195 | c := make([]byte, 1) 196 | arr.ReadAt(c, int64(int(off)+2+ii)) 197 | 198 | if c[0] == 0 { 199 | i-- 200 | } else { 201 | chars[i] = c[0] 202 | } 203 | ii++ 204 | } 205 | 206 | return string(chars) 207 | } // end of compXmlStringAt 208 | -------------------------------------------------------------------------------- /pidcat/log.go: -------------------------------------------------------------------------------- 1 | package pidcat 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "regexp" 7 | ) 8 | 9 | type LineType int 10 | 11 | const ( 12 | ProcessStart LineType = iota 13 | ProcessStop 14 | Log 15 | Backtrace 16 | ) 17 | 18 | var ( 19 | PID_START = regexp.MustCompile(`^Start proc ([a-zA-Z0-9._:]+) for ([a-z]+ [^:]+): pid=(\d+) uid=(\d+) gids=(.*)$`) 20 | PID_KILL = regexp.MustCompile(`^Killing (\d+):([a-zA-Z0-9._:]+)/[^:]+: (.*)$`) 21 | PID_LEAVE = regexp.MustCompile(`^No longer want ([a-zA-Z0-9._:]+) \(pid (\d+)\): .*$`) 22 | PID_DEATH = regexp.MustCompile(`^Process ([a-zA-Z0-9._:]+) \(pid (\d+)\) has died.?$`) 23 | LOG_LINE = regexp.MustCompile(`^([A-Z])/(.+?)\( *(\d+)\): (.*?)$`) 24 | BUG_LINE = regexp.MustCompile(`.*nativeGetEnabledTags.*`) 25 | BACKTRACE_LINE = regexp.MustCompile(`^#(.*?)pc\s(.*?)$`) 26 | ) 27 | 28 | type Line struct { 29 | Type LineType 30 | PID []byte 31 | Package []byte 32 | Level []byte 33 | Tag []byte 34 | Message []byte 35 | } 36 | 37 | func parseDeath(tag, msg []byte) ([]byte, []byte) { 38 | if bytes.Compare(tag, []byte("ActivityManager")) == 0 { 39 | var matcher *regexp.Regexp 40 | swap := false 41 | if PID_KILL.Match(msg) { 42 | matcher = PID_KILL 43 | swap = true 44 | } else if PID_LEAVE.Match(msg) { 45 | matcher = PID_LEAVE 46 | } else if PID_DEATH.Match(msg) { 47 | matcher = PID_DEATH 48 | } 49 | if matcher != nil { 50 | match := matcher.FindSubmatch(msg) 51 | pid := match[2] 52 | pack := match[1] 53 | if swap { 54 | pid = pack 55 | pack = match[2] 56 | } 57 | return pid, pack 58 | } 59 | } 60 | 61 | return nil, nil 62 | } 63 | 64 | func ParseLine(line []byte) *Line { 65 | if BUG_LINE.Match(line) || !LOG_LINE.Match(line) { 66 | return nil 67 | } 68 | var out Line 69 | 70 | logline := LOG_LINE.FindSubmatch(line) 71 | if PID_START.Match(logline[4]) { 72 | start := PID_START.FindSubmatch(logline[4]) 73 | out.PID = start[3] 74 | out.Package = start[1] 75 | out.Type = ProcessStart 76 | out.Message = []byte(fmt.Sprintf("\nProcess: %s (PID: %s) started\n", start[3], start[1])) 77 | return &out 78 | } 79 | 80 | pid, pack := parseDeath(logline[2], logline[4]) 81 | if pid != nil { 82 | out.PID = pid 83 | out.Package = pack 84 | out.Type = ProcessStop 85 | out.Message = []byte(fmt.Sprintf("\nProcess: %s (PID: %s) ended\n", pack, pid)) 86 | return &out 87 | } 88 | 89 | tag := bytes.TrimSpace(logline[2]) 90 | out.Type = Log 91 | out.PID = logline[3] 92 | out.Message = logline[4] 93 | out.Tag = tag 94 | out.Level = logline[1] 95 | return &out 96 | } 97 | -------------------------------------------------------------------------------- /pidcat/main.go: -------------------------------------------------------------------------------- 1 | package pidcat 2 | 3 | import ( 4 | "fmt" 5 | "github.com/fatih/color" 6 | "github.com/wmbest2/android/adb" 7 | "regexp" 8 | "strings" 9 | ) 10 | 11 | type colorFunc func(format string, a ...interface{}) string 12 | 13 | var ( 14 | PID_PARSER = regexp.MustCompile(`\S+\s+(\S+)(?:\s+\S+){5}\s+(?:\S\s)?(\S*)`) 15 | 16 | TagTypes map[string]string 17 | KnownTags map[string]colorFunc 18 | LastUsed []colorFunc 19 | HeaderOffset = 1 + 2 + 1 // space, level, space 20 | ) 21 | 22 | func init() { 23 | TagTypes = make(map[string]string) 24 | TagTypes[`V`] = color.New(color.FgWhite, color.BgBlack).SprintFunc()(` V `) 25 | TagTypes[`D`] = color.New(color.FgBlack, color.BgBlue).SprintFunc()(` D `) 26 | TagTypes[`I`] = color.New(color.FgBlack, color.BgGreen).SprintFunc()(` I `) 27 | TagTypes[`W`] = color.New(color.FgBlack, color.BgYellow).SprintFunc()(` W `) 28 | TagTypes[`E`] = color.New(color.FgBlack, color.BgRed).SprintFunc()(` E `) 29 | TagTypes[`F`] = color.New(color.FgBlack, color.BgRed).SprintFunc()(` F `) 30 | 31 | KnownTags = make(map[string]colorFunc) 32 | KnownTags[`dalvikvm`] = color.WhiteString 33 | 34 | LastUsed = []colorFunc{ 35 | color.RedString, color.GreenString, color.YellowString, color.BlueString, 36 | color.MagentaString, color.CyanString} 37 | } 38 | 39 | type PidCat struct { 40 | AppFilters map[string]bool 41 | pidFilters map[string]bool 42 | TagFilters []string 43 | PrettyPrint bool 44 | TagWidth int 45 | lastTag string 46 | } 47 | 48 | func NewPidCat(pretty bool, tw int) *PidCat { 49 | pid := PidCat{PrettyPrint: pretty, TagWidth: tw} 50 | pid.AppFilters = make(map[string]bool) 51 | pid.pidFilters = make(map[string]bool) 52 | return &pid 53 | } 54 | 55 | func Clear(t adb.Transporter) { 56 | adb.ShellSync(t, "logcat", "-c") 57 | } 58 | 59 | func (p *PidCat) SetAppFilters(filters ...string) { 60 | if len(filters) == 1 && filters[0] == "" { 61 | p.AppFilters = nil 62 | return 63 | } 64 | p.AppFilters = make(map[string]bool) 65 | for _, f := range filters { 66 | p.AppFilters[f] = true 67 | } 68 | } 69 | 70 | func (p *PidCat) UpdateAppFilters(t adb.Transporter) error { 71 | if p.AppFilters == nil { 72 | return nil 73 | } 74 | 75 | ps := adb.Shell(t, "ps") 76 | 77 | for line := range ps { 78 | groups := PID_PARSER.FindSubmatch(line) 79 | app := string(groups[2]) 80 | _, present := p.AppFilters[app] 81 | if present { 82 | p.pidFilters[string(groups[1])] = true 83 | } 84 | } 85 | 86 | return nil 87 | } 88 | 89 | func (p *PidCat) matches(pid []byte) bool { 90 | if len(p.AppFilters) == 0 { 91 | return true 92 | } 93 | _, present := p.pidFilters[string(pid)] 94 | return present 95 | } 96 | 97 | func (p *PidCat) matchesPackage(pack []byte) bool { 98 | if len(p.AppFilters) == 0 { 99 | return true 100 | } 101 | _, present := p.AppFilters[string(pack)] 102 | return present 103 | } 104 | 105 | func getColor(tag string) colorFunc { 106 | v, ok := KnownTags[tag] 107 | if ok { 108 | return v 109 | } 110 | color := LastUsed[0] 111 | LastUsed = append(LastUsed[1:], LastUsed[0]) 112 | return color 113 | } 114 | 115 | func (p *PidCat) Sprint(in []byte) []byte { 116 | line := ParseLine(in) 117 | 118 | if line == nil { 119 | return nil 120 | } else if line.Type == ProcessStart { 121 | if p.matchesPackage(line.Package) { 122 | p.pidFilters[string(line.PID)] = true 123 | return line.Message 124 | } 125 | return nil 126 | } else if line.Type == ProcessStop && p.matches(line.PID) { 127 | delete(p.pidFilters, string(line.PID)) 128 | return line.Message 129 | } 130 | 131 | inPid := p.matches(line.PID) 132 | if inPid && !p.PrettyPrint { 133 | return []byte(fmt.Sprintf("%s (%s): %s", line.Tag, line.PID, line.Message)) 134 | } else if !inPid { 135 | return nil 136 | } 137 | 138 | tag := string(line.Tag) 139 | if tag != p.lastTag { 140 | p.lastTag = tag 141 | colorize := getColor(tag) 142 | count := p.TagWidth - len(tag) 143 | if len(tag) > p.TagWidth { 144 | tag = tag[:p.TagWidth] 145 | count = 0 146 | } 147 | tag = fmt.Sprintf("%s%s", strings.Repeat(` `, count), tag) 148 | return []byte(fmt.Sprintln(colorize(tag), " ", TagTypes[string(line.Level)], " ", string(line.Message))) 149 | } else { 150 | tag = strings.Repeat(` `, p.TagWidth) 151 | return []byte(fmt.Sprintln(tag, " ", TagTypes[string(line.Level)], " ", string(line.Message))) 152 | } 153 | 154 | return nil 155 | } 156 | --------------------------------------------------------------------------------