-
Notifications
You must be signed in to change notification settings - Fork 666
android: extend support for protocol navigation to Android #815
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
willh-ts
wants to merge
1
commit into
main
Choose a base branch
from
will/navigate
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -51,3 +51,6 @@ libtailscale-sources.jar | |
| .DS_Store | ||
|
|
||
| tailscale.version | ||
|
|
||
| # .tmp folder | ||
| .tmp/ | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
105 changes: 105 additions & 0 deletions
105
android/src/main/java/com/tailscale/ipn/ui/util/DeepLinkNavigator.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| // Copyright (c) Tailscale Inc & AUTHORS | ||
| // SPDX-License-Identifier: BSD-3-Clause | ||
|
|
||
| package com.tailscale.ipn.ui.util | ||
|
|
||
| import android.net.Uri | ||
| import androidx.navigation.NavHostController | ||
| import com.tailscale.ipn.ui.model.Tailcfg | ||
| import com.tailscale.ipn.ui.notifier.Notifier | ||
| import com.tailscale.ipn.util.TSLog | ||
|
|
||
| // Deep links must only navigate — never perform an action on the user's | ||
| // behalf. Tails whose leaf would imply an action (e.g., a specific exit | ||
| // node) are accepted for URL compatibility but resolve to the parent | ||
| // view; the user picks and confirms in-app. | ||
| class DeepLinkNavigator(private val navController: NavHostController) { | ||
| companion object { | ||
| private const val TAG = "DeepLinkNavigator" | ||
| } | ||
|
|
||
| fun handle(uri: Uri): Boolean { | ||
| if (uri.host != "navigate") return false | ||
| val segments = uri.pathSegments | ||
| val window = segments.firstOrNull() ?: return false | ||
| val tail = segments.drop(1) | ||
|
|
||
| return when (window) { | ||
| "main" -> handleMain(tail) | ||
| "settings" -> { | ||
| showSettings() | ||
| true | ||
| } | ||
| else -> false | ||
| } | ||
| } | ||
|
|
||
| private fun handleMain(tail: List<String>): Boolean { | ||
| val tab = tail.firstOrNull() ?: return false | ||
| val rest = tail.drop(1) | ||
|
|
||
| return when (tab) { | ||
| "devices" -> | ||
| when (rest.size) { | ||
| 0 -> { | ||
| showDeviceList() | ||
| true | ||
| } | ||
| 1 -> pushDeviceDetail(rest[0]) | ||
| else -> false | ||
| } | ||
| "exit-nodes" -> | ||
| when { | ||
| rest.size <= 1 || (rest.size == 2 && rest[0] == "location") -> { | ||
| showExitNodePicker() | ||
| true | ||
| } | ||
| else -> false | ||
| } | ||
| else -> false | ||
| } | ||
| } | ||
|
|
||
| private fun showDeviceList() { | ||
| popToMain() | ||
| } | ||
|
|
||
| private fun pushDeviceDetail(identifier: String): Boolean { | ||
| val node = findNode(identifier) | ||
| if (node == null) { | ||
| TSLog.d(TAG, "Deep link: device not found for '$identifier'") | ||
| return false | ||
| } | ||
| navigateOverMain("peerDetails/${node.StableID}") | ||
| return true | ||
| } | ||
|
|
||
| private fun findNode(identifier: String): Tailcfg.Node? { | ||
| val netmap = Notifier.netmap.value ?: return null | ||
| val all = netmap.Peers.orEmpty() + netmap.SelfNode | ||
| return all.firstOrNull { identifier.equals(it.ComputedName, ignoreCase = true) } | ||
| ?: all.firstOrNull { it.StableID == identifier } | ||
| } | ||
|
|
||
| private fun showExitNodePicker() { | ||
| navigateOverMain("exitNodes") | ||
| } | ||
|
|
||
| private fun showSettings() { | ||
| navigateOverMain("settings") | ||
| } | ||
|
|
||
| private fun navigateOverMain(route: String) { | ||
| navController.navigate(route) { | ||
| popUpTo("main") { inclusive = false } | ||
| launchSingleTop = true | ||
| } | ||
| } | ||
|
|
||
| private fun popToMain() { | ||
| val popped = navController.popBackStack(route = "main", inclusive = false) | ||
| if (!popped) { | ||
| TSLog.d(TAG, "Deep link: popBackStack to 'main' returned false") | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| // Copyright (c) Tailscale Inc & AUTHORS | ||
| // SPDX-License-Identifier: BSD-3-Clause | ||
|
|
||
| // Command deeplink-probe fires a tailscale://navigate/<path> URI at an | ||
| // attached device three ways (BROWSABLE implicit, bare implicit, explicit | ||
| // component) and tails the relevant logcat lines so you can confirm | ||
| // DeepLinkNavigator saw the intent. | ||
| package main | ||
|
|
||
| import ( | ||
| "bufio" | ||
| "flag" | ||
| "fmt" | ||
| "io" | ||
| "os" | ||
| "os/exec" | ||
| "path/filepath" | ||
| "strings" | ||
| "time" | ||
| ) | ||
|
|
||
| const defaultPath = "main/devices" | ||
|
|
||
| func main() { | ||
| flag.Usage = func() { | ||
| fmt.Fprintf(os.Stderr, "Usage: %s [path-after-navigate]\n", filepath.Base(os.Args[0])) | ||
| fmt.Fprintf(os.Stderr, " default path: %s\n", defaultPath) | ||
| } | ||
| flag.Parse() | ||
|
|
||
| tail := defaultPath | ||
| if flag.NArg() >= 1 { | ||
| tail = flag.Arg(0) | ||
| } | ||
| uri := "tailscale://navigate/" + strings.TrimPrefix(tail, "/") | ||
|
|
||
| logPath := filepath.Join(os.TempDir(), "deeplink-probe.log") | ||
| logFile, err := os.Create(logPath) | ||
| if err != nil { | ||
| fmt.Fprintf(os.Stderr, "cannot create %s: %v\n", logPath, err) | ||
| os.Exit(1) | ||
| } | ||
| defer logFile.Close() | ||
|
|
||
| tee := io.MultiWriter(os.Stdout, logFile) | ||
|
|
||
| fmt.Printf("URI: %s\nLogging to %s\n\n", uri, logPath) | ||
|
|
||
| if err := exec.Command("adb", "logcat", "-c").Run(); err != nil { | ||
| fmt.Fprintf(os.Stderr, "adb logcat -c failed: %v\n", err) | ||
| } | ||
|
|
||
| fires := []struct { | ||
| label string | ||
| shell string | ||
| }{ | ||
| {"implicit (BROWSABLE)", "am start -W -a android.intent.action.VIEW -c android.intent.category.BROWSABLE -d '" + uri + "'"}, | ||
| {"implicit (no category)", "am start -W -a android.intent.action.VIEW -d '" + uri + "'"}, | ||
| {"explicit component", "am start -W -n com.tailscale.ipn/.MainActivity -a android.intent.action.VIEW -d '" + uri + "'"}, | ||
| } | ||
| for _, f := range fires { | ||
| fmt.Fprintf(tee, "=== %s ===\n", f.label) | ||
| cmd := exec.Command("adb", "shell", f.shell) | ||
| cmd.Stdout = tee | ||
| cmd.Stderr = tee | ||
| if err := cmd.Run(); err != nil { | ||
| fmt.Fprintf(tee, "adb failed: %v\n", err) | ||
| } | ||
| fmt.Fprintln(tee) | ||
| } | ||
|
|
||
| time.Sleep(time.Second) | ||
|
|
||
| fmt.Fprintln(tee, "\n=== logcat ===") | ||
| logCmd := exec.Command("adb", "logcat", "-d", "-v", "brief") | ||
| logCmd.Stderr = tee | ||
| logOut, err := logCmd.StdoutPipe() | ||
| if err != nil { | ||
| fmt.Fprintf(tee, "adb logcat pipe failed: %v\n", err) | ||
| } else if err := logCmd.Start(); err != nil { | ||
| fmt.Fprintf(tee, "adb logcat failed to start: %v\n", err) | ||
| } else { | ||
| scanner := bufio.NewScanner(logOut) | ||
| for scanner.Scan() { | ||
| line := scanner.Text() | ||
| if strings.Contains(line, "Main Activity") || strings.Contains(line, "DeepLinkNavigator") { | ||
| fmt.Fprintln(tee, line) | ||
| } | ||
| } | ||
| _ = logCmd.Wait() | ||
| } | ||
|
|
||
| fmt.Printf("\nFull output: %s\n", logPath) | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.