-
-
Notifications
You must be signed in to change notification settings - Fork 47
ADFA-5067: Support deep links to open projects and files #1651
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
davidschachterADFA
wants to merge
42
commits into
stage
Choose a base branch
from
task/ADFA-5067-deep-links
base: stage
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
Show all changes
42 commits
Select commit
Hold shift + click to select a range
2df5a56
ADFA-5067 | Add deep-link request models, path-traversal guard, and b…
davidschachterADFA 6b96c84
ADFA-5067 | Add DeepLinkActivity as the sole App Link entry point
davidschachterADFA 8c42c35
ADFA-5067 | Handle deep links with no project open in MainActivity
davidschachterADFA 0df3845
ADFA-5067 | Handle deep links to an already-open project in EditorHan…
davidschachterADFA 1109bf1
ADFA-5067 | Add RFC 5785 .well-known/assetlinks.json for App Links ve…
davidschachterADFA a0790b2
ADFA-5067 | Document the deep-link entry point in ARCHITECTURE.md
davidschachterADFA 1078604
ADFA-5067 | Fix deep-link routing race in DeepLinkActivity
davidschachterADFA aea677b
ADFA-5067 | Guard MainActivity's deep-link handling against recreation
davidschachterADFA 3e8fd65
ADFA-5067 | Prevent stacked confirm-close dialogs from dropping a dee…
davidschachterADFA 045aa00
ADFA-5067 | Fix reserved-keyword collision in DeepLinkRequest.parse()
davidschachterADFA ab4be5e
ADFA-5067 | Close symlink escape in resolveWithinDirectory
davidschachterADFA 3ad035b
ADFA-5067 | Sync ARCHITECTURE.md with the DeepLinkActivity routing fix
davidschachterADFA 0f5b682
ADFA-5067 | Acquire RecentProjectDao through Koin, not a raw DB call
davidschachterADFA ee35586
ADFA-5067 | Show a Toast when a deep link fails to parse
davidschachterADFA 4196a34
ADFA-5067 | Handle SecurityException scanning projects for a deep link
davidschachterADFA cc74e65
ADFA-5067 | Don't let a Recents-write failure crash the app
davidschachterADFA b68b50a
ADFA-5067 | Name deliberately-unused catch bindings "_"
davidschachterADFA 45d94cd
ADFA-5067 | Add more reserved-keyword-collision regression cases
davidschachterADFA a451470
ADFA-5067 | Fix three deep-link close/open correctness gaps
davidschachterADFA df705c9
ADFA-5067 | Match line/column from the end of the path, not the start
davidschachterADFA de0e9e8
ADFA-5067 | Add embedded-keyword regression tests; use Truth in this …
davidschachterADFA 86c1f70
ADFA-5067 | Block a new confirm-close while a save-and-close is in fl…
davidschachterADFA 9741df7
ADFA-5067 | Remove dead saveProjectToRecents(); Koin-provide PendingD…
davidschachterADFA e9a1afb
ADFA-5067 | Look up a deep-linked project by name directly, not by sc…
davidschachterADFA f8cb2c9
ADFA-5067 | Deduplicate deep-link line/column parsing
davidschachterADFA 11d1988
ADFA-5067 | Fix path traversal introduced by findValidProjectByName
davidschachterADFA a44feeb
ADFA-5067 | Narrow the Recents-insert catch to SQLException
davidschachterADFA 6a92920
ADFA-5067 | Document MainViewModel's screen-state and event contracts
davidschachterADFA 7e92715
ADFA-5067 | Dismiss the confirm-close dialog in onDestroy()
davidschachterADFA fa73614
ADFA-5067 | Always invoke saveAllAsync's runAfter, even if saveAll th…
davidschachterADFA 2a9c28a
ADFA-5067 | Reject overlapping confirm-close requests instead of hija…
davidschachterADFA 3b7afd7
ADFA-5067 | Fix same-project fast path; dedupe deep-link project lookup
davidschachterADFA dbf4f55
ADFA-5067 | Drain the pending file request even when sync fails
davidschachterADFA 8eb75ca
ADFA-5067 | ActionContextProvider never hands back a finishing activity
davidschachterADFA 06751ad
ADFA-5067 | Add CLEAR_TOP so repeated deep links don't stack MainActi…
davidschachterADFA df7d7b4
ADFA-5067 | Reject "." and embedded separators in a deep-link project…
davidschachterADFA 8343ea9
ADFA-5067 | Widen the Recents-insert catch back to Throwable
davidschachterADFA de62fac
ADFA-5067 | Document the full deep-link routing/file-open flow
davidschachterADFA de7ad7f
Merge remote-tracking branch 'origin/stage' into task/ADFA-5067-deep-…
davidschachterADFA 7a89bd6
ADFA-5067 | Use the inherited SLF4J logger, not android.util.Log
davidschachterADFA cfb708e
Merge branch 'stage' into task/ADFA-5067-deep-links
davidschachterADFA 44d0dbc
Merge branch 'stage' into task/ADFA-5067-deep-links
davidschachterADFA 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
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 |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| # `.well-known` (ADFA-5067) | ||
|
|
||
| `assetlinks.json` in this directory is the [RFC 5785](https://www.rfc-editor.org/rfc/rfc5785) / | ||
| [Digital Asset Links](https://developers.google.com/digital-asset-links) file required for Android | ||
| App Links to `https://www.appdevforall.org/device/open/project/...` to auto-verify. | ||
|
|
||
| This directory lives in the repo only until the actual website exists. To activate it: | ||
|
|
||
| 1. Copy this directory verbatim to the web server root, so it serves at | ||
| `https://www.appdevforall.org/.well-known/assetlinks.json` with `Content-Type: application/json`. | ||
| 2. Replace the `TODO_REPLACE_WITH_RELEASE_SIGNING_SHA256_FINGERPRINT` placeholder with the SHA-256 | ||
| fingerprint of the certificate that actually signs the released APK/AAB — get it via | ||
| `keytool -list -v -keystore <release.jks>` (whoever holds the release keystore), or from the Play | ||
| Console under **App integrity > App signing key certificate** if Play App Signing is used. This | ||
| cannot be filled in from source; it's a secret held by release engineering, not derivable from this | ||
| repository. | ||
|
|
||
| Until both steps are done, `android:autoVerify="true"` on `DeepLinkActivity`'s intent-filter will fail | ||
| Digital Asset Links verification, and Android may show a disambiguation chooser instead of opening the | ||
| app directly when a link is tapped. This is expected for now. |
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,12 @@ | ||
| [ | ||
| { | ||
| "relation": ["delegate_permission/common.handle_all_urls"], | ||
| "target": { | ||
| "namespace": "android_app", | ||
| "package_name": "com.itsaky.androidide", | ||
| "sha256_cert_fingerprints": [ | ||
| "TODO_REPLACE_WITH_RELEASE_SIGNING_SHA256_FINGERPRINT" | ||
| ] | ||
| } | ||
| } | ||
| ] | ||
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
83 changes: 83 additions & 0 deletions
83
app/src/main/java/com/itsaky/androidide/activities/DeepLinkActivity.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,83 @@ | ||
| /* | ||
| * This file is part of AndroidIDE. | ||
| * | ||
| * AndroidIDE is free software: you can redistribute it and/or modify | ||
| * it under the terms of the GNU General Public License as published by | ||
| * the Free Software Foundation, either version 3 of the License, or | ||
| * (at your option) any later version. | ||
| * | ||
| * AndroidIDE is distributed in the hope that it will be useful, | ||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| * GNU General Public License for more details. | ||
| * | ||
| * You should have received a copy of the GNU General Public License | ||
| * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. | ||
| */ | ||
|
|
||
| package com.itsaky.androidide.activities | ||
|
|
||
| import android.app.Activity | ||
| import android.content.Intent | ||
| import android.os.Bundle | ||
| import android.widget.Toast | ||
| import com.itsaky.androidide.activities.editor.EditorActivityKt | ||
| import com.itsaky.androidide.api.ActionContextProvider | ||
| import com.itsaky.androidide.models.DeepLinkRequest | ||
| import com.itsaky.androidide.resources.R.string | ||
|
|
||
| /** | ||
| * The sole `<intent-filter>` holder for `https://www.appdevforall.org/device/open/project/...` App | ||
| * Links. Never shows any UI -- it only parses the incoming [android.net.Uri], decides whether a | ||
| * project is already loaded, and hands off to whichever real activity owns that scenario: | ||
| * [MainActivity] if nothing is open yet, or the already-running [EditorActivityKt] (via its | ||
| * `singleTask` `onNewIntent`) if one is. | ||
| * | ||
| * Kept as a plain [Activity] (like [SplashActivity]), not [com.itsaky.androidide.app.BaseIDEActivity], | ||
| * since it never calls `setContentView` and has no theming needs of its own. | ||
| */ | ||
| class DeepLinkActivity : Activity() { | ||
| override fun onCreate(savedInstanceState: Bundle?) { | ||
| super.onCreate(savedInstanceState) | ||
|
|
||
| val request = DeepLinkRequest.parse(intent?.data) | ||
| if (request == null) { | ||
| // A Toast, not flashError -- this activity finishes immediately below, tearing down its | ||
| // window before a view-based Flashbar could ever render. | ||
| Toast.makeText(this, getString(string.msg_deeplink_invalid_link), Toast.LENGTH_LONG).show() | ||
| finish() | ||
| return | ||
| } | ||
|
|
||
| // ActionContextProvider tracks the live EditorHandlerActivity instance (set in its | ||
| // onResume, cleared in onDestroy) -- this reflects "is an editor actually on screen", | ||
| // unlike IProjectManager's workspace, which stays null for the whole duration of a | ||
| // Gradle sync even while EditorActivityKt is already open and visible. | ||
| val target = | ||
|
davidschachterADFA marked this conversation as resolved.
|
||
| if (ActionContextProvider.getActivity() != null) { | ||
| EditorActivityKt::class.java | ||
| } else { | ||
| MainActivity::class.java | ||
| } | ||
|
|
||
| startActivity( | ||
| Intent(this, target).apply { | ||
| putExtra(DeepLinkRequest.EXTRA_KEY, request) | ||
| // If `target` is MainActivity and one already exists in the task, reuse it via | ||
| // onNewIntent instead of stacking a second instance -- SINGLE_TOP alone isn't enough | ||
| // here, since DeepLinkActivity (not MainActivity) is what's actually on top of the | ||
| // stack at this exact call, so SINGLE_TOP's "already at the top" check never matches; | ||
| // CLEAR_TOP finds MainActivity anywhere in the task and reuses it via onNewIntent | ||
| // (combined with SINGLE_TOP, rather than the destroy-and-recreate CLEAR_TOP alone | ||
| // would do). EditorActivityKt is singleTask, so it always reuses its live instance | ||
| // regardless of these flags. | ||
| addFlags( | ||
| Intent.FLAG_ACTIVITY_NEW_TASK or | ||
| Intent.FLAG_ACTIVITY_SINGLE_TOP or | ||
| Intent.FLAG_ACTIVITY_CLEAR_TOP, | ||
| ) | ||
| }, | ||
| ) | ||
| finish() | ||
| } | ||
| } | ||
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
Oops, something went wrong.
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.