Add GSI Loader, APK Audit, Firmware, Intent Lab, Screen Mirror, Magisk, and App Lock features; bump to v1.2.0
- New sidebar views: GSI Loader (DSU + permanent fastboot flash), APK Audit, Firmware, Intent Lab, Screen Mirror - New backend modules: backend_gsi.go, backend_apkaudit*.go, backend_firmware.go, backend_intent.go, backend_magisk.go, backend_applock.go, backend_scrcpy.go, backend_payload.go, backend_privacy.go, backend_overview.go, backend_bootinfo.go, backend_flasher.go, backend_transfer.go, backend_filehttp.go - App Lock / danger-gate infrastructure (DangerGate, LockGate, applock.ts) - Privileged uninstall via embedded Android helper dex (android-helper/) - nfpm packaging version bump to 1.2.0
This commit is contained in:
parent
10902567d8
commit
26d82c68d5
136 changed files with 18263 additions and 2835 deletions
123
backend_intent.go
Normal file
123
backend_intent.go
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
package main
|
||||
|
||||
// Intent Lab — list an app's launchable (exported) activities and start them via
|
||||
// `am start`, plus a free-form implicit-intent launcher. Lets a user reach hidden
|
||||
// settings menus / internal screens that aren't on the launcher.
|
||||
//
|
||||
// Activity discovery uses `dumpsys package <pkg>`: components that appear in the
|
||||
// Activity Resolver Table have an intent filter, so they're launchable by the
|
||||
// shell user. Non-filtered/exported=false activities generally can't be started
|
||||
// from adb; the launcher surfaces the real `am start` result either way.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type IntentActivity struct {
|
||||
Name string `json:"name"` // activity class, relative to the package
|
||||
Component string `json:"component"` // full "package/activity" target for am start
|
||||
Exported bool `json:"exported"` // appears in the resolver table (has an intent filter)
|
||||
}
|
||||
|
||||
// componentRe guards the `am start -n <component>` target against shell injection
|
||||
// on the device side (adb shell reparses the command).
|
||||
var componentRe = regexp.MustCompile(`^[A-Za-z0-9_.]+/[A-Za-z0-9_.$]+$`)
|
||||
var actionRe = regexp.MustCompile(`^[A-Za-z0-9_.]+$`)
|
||||
|
||||
// ListActivities returns the launchable activities of an installed package.
|
||||
func (a *App) ListActivities(packageName string) ([]IntentActivity, error) {
|
||||
if err := validatePackageName(packageName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dump, err := a.runAdbShellTimeout(30*1e9, "dumpsys", "package", packageName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query package: %w", err)
|
||||
}
|
||||
|
||||
prefix := packageName + "/"
|
||||
seen := map[string]bool{}
|
||||
var acts []IntentActivity
|
||||
inActivities := false
|
||||
|
||||
for _, line := range strings.Split(dump, "\n") {
|
||||
t := strings.TrimSpace(line)
|
||||
switch {
|
||||
case strings.Contains(t, "Activity Resolver Table"):
|
||||
inActivities = true
|
||||
continue
|
||||
case strings.Contains(t, "Receiver Resolver Table"),
|
||||
strings.Contains(t, "Service Resolver Table"),
|
||||
strings.Contains(t, "Provider Resolver Table"),
|
||||
strings.Contains(t, "Preferred Activities"),
|
||||
strings.Contains(t, "Key Set Manager"):
|
||||
inActivities = false
|
||||
}
|
||||
if !inActivities {
|
||||
continue
|
||||
}
|
||||
for _, tok := range strings.Fields(t) {
|
||||
if strings.HasPrefix(tok, prefix) && len(tok) > len(prefix) && !seen[tok] {
|
||||
seen[tok] = true
|
||||
acts = append(acts, IntentActivity{
|
||||
Name: strings.TrimPrefix(tok, prefix),
|
||||
Component: tok,
|
||||
Exported: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(acts, func(i, j int) bool { return acts[i].Name < acts[j].Name })
|
||||
return acts, nil
|
||||
}
|
||||
|
||||
// StartActivity launches an explicit component ("package/activity").
|
||||
func (a *App) StartActivity(component string) (string, error) {
|
||||
component = strings.TrimSpace(component)
|
||||
if !componentRe.MatchString(component) {
|
||||
return "", fmt.Errorf("invalid component, expected package/activity: %s", component)
|
||||
}
|
||||
out, err := a.runAdbShell("am", "start", "-n", component)
|
||||
return interpretAmResult(out, err)
|
||||
}
|
||||
|
||||
// StartIntentAction launches an implicit intent by action, with an optional data URI.
|
||||
func (a *App) StartIntentAction(action, data string) (string, error) {
|
||||
action = strings.TrimSpace(action)
|
||||
if action == "" || !actionRe.MatchString(action) {
|
||||
return "", fmt.Errorf("invalid or empty action")
|
||||
}
|
||||
args := []string{"am", "start", "-a", action}
|
||||
if data = strings.TrimSpace(data); data != "" {
|
||||
// Reject shell metacharacters — adb shell reparses this on the device.
|
||||
if strings.ContainsAny(data, " \t\n\r;&|`$<>()\"'\\") {
|
||||
return "", fmt.Errorf("data URI contains disallowed characters")
|
||||
}
|
||||
args = append(args, "-d", data)
|
||||
}
|
||||
out, err := a.runAdbShell(args...)
|
||||
return interpretAmResult(out, err)
|
||||
}
|
||||
|
||||
// interpretAmResult normalises `am start` output — it often prints errors to
|
||||
// stdout with a zero exit, so inspect the text as well as err.
|
||||
func interpretAmResult(out string, err error) (string, error) {
|
||||
out = strings.TrimSpace(out)
|
||||
if err != nil {
|
||||
if out != "" {
|
||||
return "", fmt.Errorf("%s", firstLine(out))
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
if strings.Contains(out, "Error:") || strings.Contains(out, "Exception") ||
|
||||
strings.Contains(out, "does not exist") || strings.Contains(out, "Permission Denial") {
|
||||
return "", fmt.Errorf("%s", firstLine(out))
|
||||
}
|
||||
if out == "" {
|
||||
out = "Started."
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue