mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-04 20:17:09 +00:00
feat(android): detect foreground package via adb dumpsys
This commit is contained in:
1 parent
4d26bdf00d
commit
09528d84e6
2 files changed
+69
No files matched your search
@@ -8,6 +8,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -236,6 +237,38 @@ func waitForBoot(ctx context.Context, timeout time.Duration) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ForegroundPackage returns the package of the currently resumed activity on
|
||||||
|
// the connected device, or "" when it cannot be determined.
|
||||||
|
func ForegroundPackage(ctx context.Context) (string, error) {
|
||||||
|
adb, err := AdbBinary()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
output, err := exec.CommandContext(ctx, adb, "shell", "dumpsys", "activity", "activities").Output()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return parseForegroundPackage(string(output)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// resumedActivityPackage matches the "<package>/<activity>" component name that
|
||||||
|
// dumpsys prints on ResumedActivity lines, capturing the package.
|
||||||
|
var resumedActivityPackage = regexp.MustCompile(`([a-zA-Z][a-zA-Z0-9_.]*)/[a-zA-Z0-9_.$]+`)
|
||||||
|
|
||||||
|
// parseForegroundPackage extracts the foreground package from `dumpsys activity
|
||||||
|
// activities` output by reading the first ResumedActivity component name.
|
||||||
|
func parseForegroundPackage(dumpsys string) string {
|
||||||
|
for line := range strings.SplitSeq(dumpsys, "\n") {
|
||||||
|
if !strings.Contains(line, "ResumedActivity") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if match := resumedActivityPackage.FindStringSubmatch(line); match != nil {
|
||||||
|
return match[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func bootCompleted(ctx context.Context) (bool, error) {
|
func bootCompleted(ctx context.Context) (bool, error) {
|
||||||
adb, err := AdbBinary()
|
adb, err := AdbBinary()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -91,3 +91,39 @@ func TestPathContains(t *testing.T) {
|
|||||||
t.Error("did not expect /nope in PATH")
|
t.Error("did not expect /nope in PATH")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseForegroundPackage(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
dumpsys string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "mResumedActivity folio",
|
||||||
|
dumpsys: " Stack #0:\n mResumedActivity: ActivityRecord{a1b2c3 u0 app.folio/.MainActivity t42}\n",
|
||||||
|
want: "app.folio",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "topResumedActivity chrome",
|
||||||
|
dumpsys: "ResumedActivity: ActivityRecord{ff u0 com.android.chrome/com.google.android.apps.chrome.Main t9}\n topResumedActivity=ActivityRecord{ff u0 com.android.chrome/com.google.android.apps.chrome.Main}",
|
||||||
|
want: "com.android.chrome",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "launcher",
|
||||||
|
dumpsys: " mResumedActivity: ActivityRecord{x u0 com.google.android.apps.nexuslauncher/.NexusLauncherActivity t1}",
|
||||||
|
want: "com.google.android.apps.nexuslauncher",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no resumed activity",
|
||||||
|
dumpsys: " some unrelated dumpsys output\n with no resumed line\n",
|
||||||
|
want: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
if got := parseForegroundPackage(tc.dumpsys); got != tc.want {
|
||||||
|
t.Errorf("parseForegroundPackage = %q, want %q", got, tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user