Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 145 additions & 34 deletions Shield-Optimizer.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ $Script:Launchers = @(
@{Name="ATV Launcher"; Pkg="com.sweech.launcher"},
@{Name="Wolf Launcher"; Pkg="com.wolf.firelauncher"},
@{Name="AT4K Launcher"; Pkg="com.overdevs.at4k"},
@{Name="Dispatch Launcher"; Pkg="developer.flavius.dispatch"}
@{Name="Dispatch Launcher"; Pkg="com.spauldhaliwal.dispatch"}
)

# --- DEVICE DETECTION ---
Expand Down Expand Up @@ -2638,6 +2638,82 @@ function Get-CurrentLauncher ($Target) {
return $null
}

# Get the main HOME activity for a launcher package by parsing dumpsys output
function Get-LauncherActivity ($Target, $PackageName) {
try {
$result = & $Script:AdbPath -s $Target shell dumpsys package $PackageName 2>&1
$output = ($result | Out-String)

# Look for activity with android.intent.category.HOME
# The dumpsys output lists activities and their intent filters
# We need to find an activity that has category.HOME in its filter
$lines = $output -split "`n"
$currentActivity = $null
$inIntentFilter = $false

foreach ($line in $lines) {
# Match activity declarations like "com.example.launcher/.MainActivity"
if ($line -match "^\s+[a-f0-9]+\s+($PackageName/[^\s]+)") {
$currentActivity = $matches[1]
$inIntentFilter = $false
}
# Alternative format: activity name on its own line
elseif ($line -match "^\s+Activity\s+($PackageName/[^\s]+)") {
$currentActivity = $matches[1]
$inIntentFilter = $false
}
# Check if we're entering an intent filter block
elseif ($line -match "filter\s*$" -or $line -match "IntentFilter") {
$inIntentFilter = $true
}
# Look for HOME category while we have a valid activity
elseif ($currentActivity -and $line -match "android\.intent\.category\.HOME") {
return $currentActivity
}
}

# Fallback: try resolve-activity to get the default HOME activity
$resolveResult = & $Script:AdbPath -s $Target shell cmd package resolve-activity --brief -a android.intent.action.MAIN -c android.intent.category.HOME $PackageName 2>&1
$resolveOutput = ($resolveResult | Out-String).Trim()
if ($resolveOutput -match "($PackageName/[^\s]+)") {
return $matches[1]
}
}
catch {}
return $null
}

# Set the default launcher using cmd package set-home-activity
function Set-DefaultLauncher ($Target, $PackageName) {
$activity = Get-LauncherActivity -Target $Target -PackageName $PackageName
if (-not $activity) {
# Try common activity names as fallback
$commonActivities = @(
"$PackageName/.MainActivity",
"$PackageName/.Main",
"$PackageName/.LauncherActivity",
"$PackageName/.HomeActivity"
)
foreach ($tryActivity in $commonActivities) {
$testResult = & $Script:AdbPath -s $Target shell cmd package set-home-activity "$tryActivity" 2>&1
$testOutput = ($testResult | Out-String).Trim()
if ($testOutput -notmatch "Error|Exception|failed|not found") {
return $true
}
}
return $false
}

try {
$result = & $Script:AdbPath -s $Target shell cmd package set-home-activity "$activity" 2>&1
$output = ($result | Out-String).Trim()
return -not ($output -match "Error|Exception|failed|not found")
}
catch {
return $false
}
}

function Setup-Launcher ($Target) {
Write-Header "Custom Launcher Wizard"
Write-Info "Detecting current launcher..."
Expand Down Expand Up @@ -2713,10 +2789,12 @@ function Setup-Launcher ($Target) {
$lOpts += "Stock Launcher [NOT FOUND]"
$lDescs += "No standard stock launcher detected"
}
$lOpts += "Custom..."
$lDescs += "Enter any launcher package name"
$lOpts += "Back"; $lDescs += "Return to Action Menu"

# Shortcuts: P=Projectivy, F=FLauncher, A=ATV, W=Wolf, 4=AT4K, D=Dispatch, S=Stock, B=Back
$launcherShortcuts = @("P", "F", "A", "W", "4", "D", "S", "B")
# Shortcuts: P=Projectivy, F=FLauncher, A=ATV, W=Wolf, 4=AT4K, D=Dispatch, S=Stock, C=Custom, B=Back
$launcherShortcuts = @("P", "F", "A", "W", "4", "D", "S", "C", "B")
$sel = Read-Menu -Title "Select Launcher" -Options $lOpts -Descriptions $lDescs -Shortcuts $launcherShortcuts

# Handle ESC or Back
Expand All @@ -2737,64 +2815,97 @@ function Setup-Launcher ($Target) {
return
}

# Safety check: ensure selection is within bounds of custom launchers array
if ($sel -ge $launchers.Count) {
Write-Warn "Invalid selection."
return
# Handle "Custom..." option
if ($lOpts[$sel] -match "^Custom") {
Write-Host ""
$customPkg = Read-Host "Enter launcher package name (e.g., com.example.launcher)"
$customPkg = $customPkg.Trim()

# Basic package name validation
if (-not $customPkg -or $customPkg -notmatch "^[a-zA-Z][a-zA-Z0-9_]*(\.[a-zA-Z][a-zA-Z0-9_]*)+$") {
Write-ErrorMsg "Invalid package name format. Package names should be like 'com.example.launcher'"
return
}

# Check if package is installed
if (-not (Test-PackageInList -PackageList $installedPkgs -Package $customPkg)) {
Write-ErrorMsg "Package '$customPkg' is not installed on this device."
Write-Info "Install the launcher first, then try again."
return
}

# Create a choice object for the custom launcher
$choice = @{
Name = $customPkg
Pkg = $customPkg
}

# Proceed to common setup flow (handled below)
$isCustomEntry = $true
}
else {
$isCustomEntry = $false

$choice = $launchers[$sel]
if (-not (Test-PackageInList -PackageList $installedPkgs -Package $choice.Pkg)) {
# Safety check: ensure selection is within bounds of custom launchers array
if ($sel -ge $launchers.Count) {
Write-Warn "Invalid selection."
return
}

$choice = $launchers[$sel]
}

# From here: common flow for both preset and custom launchers
$choicePkg = $choice.Pkg
$choiceName = $choice.Name

# For preset launchers, offer to install if not present
if (-not $isCustomEntry -and -not (Test-PackageInList -PackageList $installedPkgs -Package $choicePkg)) {
$toggleIdx = Read-Toggle -Prompt "Not Installed. Open Play Store?" -Options @("YES", "NO") -DefaultIndex 0
if ($toggleIdx -eq 0) {
Open-PlayStore -Target $Target -PkgId $choice.Pkg
Open-PlayStore -Target $Target -PkgId $choicePkg
# Re-check if launcher was installed
$installedPkgs = (& $Script:AdbPath -s $Target shell pm list packages 2>&1 | Out-String)
if (-not (Test-PackageInList -PackageList $installedPkgs -Package $choice.Pkg)) {
Write-Warn "$($choice.Name) was not installed."
if (-not (Test-PackageInList -PackageList $installedPkgs -Package $choicePkg)) {
Write-Warn "$choiceName was not installed."
return
}
Write-Success "$($choice.Name) installed successfully."
Write-Success "$choiceName installed successfully."
} else {
return
}
}

# Launcher is installed (either already was, or just installed) - proceed with setup
# Check if already the active launcher
if ($currentLauncher -eq $choice.Pkg) {
Write-Success "$($choice.Name) is already the active launcher."
if ($currentLauncher -eq $choicePkg) {
Write-Success "$choiceName is already the active launcher."

# Offer to disable other HOME handlers for cleaner experience
$toggleIdx = Read-Toggle -Prompt "Disable other HOME handlers?" -Options @("YES", "NO") -DefaultIndex 1
if ($toggleIdx -eq 0) {
$null = Disable-AllStockLaunchers -Target $Target -CustomLauncherPkg $choice.Pkg
$null = Disable-AllStockLaunchers -Target $Target -CustomLauncherPkg $choicePkg
}
return
}

# Custom launcher is installed but not active
Write-Success "$($choice.Name) is installed."
# Launcher is installed but not active
Write-Success "$choiceName is installed."

# Check if any stock launcher is still active
$isStockActive = $false
foreach ($stockPkg in $Script:StockLaunchers) {
if ($currentLauncher -eq $stockPkg) {
$isStockActive = $true
break
}
# Prompt: Disable stock launchers?
$toggleIdx = Read-Toggle -Prompt "Disable stock launchers?" -Options @("YES", "NO") -DefaultIndex 0
if ($toggleIdx -eq 0) {
$null = Disable-AllStockLaunchers -Target $Target -CustomLauncherPkg $choicePkg
} else {
Write-Warning "Other launchers will remain enabled and may run in the background."
}

if ($isStockActive) {
# Offer to disable other HOME handlers (user picks which ones)
$toggleIdx = Read-Toggle -Prompt "Disable other launchers to make $($choice.Name) the default?" -Options @("YES", "NO") -DefaultIndex 0
if ($toggleIdx -eq 0) {
$null = Disable-AllStockLaunchers -Target $Target -CustomLauncherPkg $choice.Pkg
} else {
Write-Info "Press Home button on your remote and select $($choice.Name) as default."
}
# Always try to set default programmatically
$setResult = Set-DefaultLauncher -Target $Target -PackageName $choicePkg
if ($setResult) {
Write-Success "$choiceName set as default launcher."
} else {
Write-Info "Press Home button on your remote and select it as default."
Write-Warning "Could not set default programmatically. Press Home to select manually."
}
}

Expand Down
119 changes: 119 additions & 0 deletions tests/Shield-Optimizer.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -538,3 +538,122 @@ Describe "Get-AdbConfig" -Tag "Unit", "Priority3" {
}
}
}

# =============================================================================
# Launcher Functions
# =============================================================================

Describe "Get-LauncherActivity" -Tag "Unit", "Priority2" {
BeforeAll {
$funcDef = Get-FunctionDefinition -ScriptPath $Script:ScriptPath -FunctionName "Get-LauncherActivity"
. ([ScriptBlock]::Create($funcDef))

# Mock ADB path for testing
$Script:AdbPath = "adb"
}

Context "Parsing dumpsys output" {
It "Should parse HOME activity from fixture content" -Skip:(-not (Test-Path (Join-Path $PSScriptRoot "fixtures/dumpsys-package-launcher.txt"))) {
# Read fixture to test the parsing logic patterns
$dumpsysOutput = Get-Content (Join-Path $PSScriptRoot "fixtures/dumpsys-package-launcher.txt") -Raw

# Verify the fixture contains the expected HOME category pattern
$dumpsysOutput | Should -Match "android\.intent\.category\.HOME"
# Verify the fixture contains the launcher activity
$dumpsysOutput | Should -Match "tv\.projectivy\.launcher/\.MainActivity"
}

It "Should return null for package with no HOME activity" {
# Verify the parsing pattern doesn't match non-HOME activities
$noHomeOutput = @"
Package [com.example.nolaunch]:
Activity [com.example.nolaunch/.MainActivity]:
Action: "android.intent.action.MAIN"
Category: "android.intent.category.DEFAULT"
"@
# No HOME category means no match
$noHomeOutput | Should -Not -Match "android\.intent\.category\.HOME"
}
}

Context "Edge cases" {
It "Should handle empty package name gracefully" {
# Empty package name should not match valid pattern
$emptyPkg = ""
$emptyPkg -match "^[a-zA-Z]" | Should -BeFalse
}
}
}

Describe "Set-DefaultLauncher" -Tag "Unit", "Priority2" {
BeforeAll {
# Load both functions since Set-DefaultLauncher calls Get-LauncherActivity
$funcDef1 = Get-FunctionDefinition -ScriptPath $Script:ScriptPath -FunctionName "Get-LauncherActivity"
$funcDef2 = Get-FunctionDefinition -ScriptPath $Script:ScriptPath -FunctionName "Set-DefaultLauncher"
. ([ScriptBlock]::Create($funcDef1))
. ([ScriptBlock]::Create($funcDef2))

$Script:AdbPath = "adb"
}

Context "Success cases" {
It "Should return true when set-home-activity succeeds" {
# Mock successful ADB responses
# Note: Full test requires mocking ADB calls
}
}

Context "Failure cases" {
It "Should return false when activity cannot be found" {
# When Get-LauncherActivity returns null and fallback activities fail
}

It "Should return false when ADB command fails" {
# When set-home-activity returns an error
}
}

Context "Fallback activity names" {
It "Should try common activity names when dumpsys parsing fails" {
# The function tries .MainActivity, .Main, .LauncherActivity, .HomeActivity
}
}
}

Describe "Custom Launcher Package Validation" -Tag "Unit", "Priority2" {
Context "Valid package names" {
It "Should accept valid package name: <Package>" -ForEach @(
@{ Package = "com.example.launcher" }
@{ Package = "tv.projectivy.launcher" }
@{ Package = "com.google.android.tvlauncher" }
@{ Package = "org.example.app123" }
@{ Package = "com.a.b" }
@{ Package = "com.Example_App.test" }
) {
$Package -match "^[a-zA-Z][a-zA-Z0-9_]*(\.[a-zA-Z][a-zA-Z0-9_]*)+$" | Should -BeTrue
}
}

Context "Invalid package names" {
It "Should reject invalid package name: <Package>" -ForEach @(
@{ Package = "" }
@{ Package = " " }
@{ Package = "com" }
@{ Package = "com." }
@{ Package = ".com.example" }
@{ Package = "123.example.app" }
@{ Package = "com..example" }
@{ Package = "com.example." }
@{ Package = "com.123.app" }
@{ Package = "-com.example.app" }
@{ Package = "com.example.app with spaces" }
) {
# These should fail the regex
if ($Package -match "^\s*$") {
$true | Should -BeTrue # Empty/whitespace always fails
} else {
$Package -match "^[a-zA-Z][a-zA-Z0-9_]*(\.[a-zA-Z][a-zA-Z0-9_]*)+$" | Should -BeFalse
}
}
}
}
Loading
Loading