forked from mozilla-mobile/android-components
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
PWA: Add activities for routing and handling standalone/fullscreen apps.
* Closes mozilla-mobile#2291 * Closes mozilla-mobile#1830
- Loading branch information
Showing
7 changed files
with
414 additions
and
1 deletion.
There are no files selected for viewing
This file contains 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 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 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
70 changes: 70 additions & 0 deletions
70
...s/feature/pwa/src/main/java/mozilla/components/feature/pwa/AbstractWebAppShellActivity.kt
This file contains 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,70 @@ | ||
/* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||
|
||
package mozilla.components.feature.pwa | ||
|
||
import android.os.Bundle | ||
import android.support.annotation.VisibleForTesting | ||
import android.support.v7.app.AppCompatActivity | ||
import mozilla.components.browser.session.Session | ||
import mozilla.components.browser.session.SessionManager | ||
import mozilla.components.browser.session.manifest.WebAppManifest | ||
import mozilla.components.concept.engine.Engine | ||
import mozilla.components.concept.engine.EngineView | ||
import mozilla.components.feature.pwa.ext.applyOrientation | ||
import mozilla.components.support.ktx.android.view.enterToImmersiveMode | ||
|
||
/** | ||
* Activity for "standalone" and "fullscreen" web applications. | ||
*/ | ||
abstract class AbstractWebAppShellActivity : AppCompatActivity() { | ||
abstract val engine: Engine | ||
abstract val sessionManager: SessionManager | ||
|
||
lateinit var session: Session | ||
lateinit var engineView: EngineView | ||
|
||
override fun onCreate(savedInstanceState: Bundle?) { | ||
super.onCreate(savedInstanceState) | ||
|
||
// We do not "install" web apps yet. So there's no place we can load a manifest from yet. | ||
// https://github.com/mozilla-mobile/android-components/issues/2382 | ||
val manifest = createTestManifest() | ||
|
||
applyConfiguration(manifest) | ||
renderSession(manifest) | ||
} | ||
|
||
override fun onDestroy() { | ||
super.onDestroy() | ||
|
||
sessionManager | ||
.getOrCreateEngineSession(session) | ||
.close() | ||
} | ||
|
||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) | ||
internal fun applyConfiguration(manifest: WebAppManifest) { | ||
if (manifest.display == WebAppManifest.DisplayMode.FULLSCREEN) { | ||
enterToImmersiveMode() | ||
} | ||
|
||
applyOrientation(manifest) | ||
} | ||
|
||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) | ||
internal fun renderSession(manifest: WebAppManifest) { | ||
setContentView(engine | ||
.createView(this) | ||
.also { engineView = it } | ||
.asView()) | ||
|
||
session = Session(manifest.startUrl) | ||
engineView.render(sessionManager.getOrCreateEngineSession(session)) | ||
} | ||
|
||
companion object { | ||
const val INTENT_ACTION = "mozilla.components.feature.pwa.SHELL" | ||
} | ||
} |
92 changes: 92 additions & 0 deletions
92
...onents/feature/pwa/src/main/java/mozilla/components/feature/pwa/WebAppLauncherActivity.kt
This file contains 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,92 @@ | ||
/* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||
|
||
package mozilla.components.feature.pwa | ||
|
||
import android.content.ActivityNotFoundException | ||
import android.content.Intent | ||
import android.os.Bundle | ||
import android.support.annotation.VisibleForTesting | ||
import android.support.annotation.VisibleForTesting.PRIVATE | ||
import android.support.v7.app.AppCompatActivity | ||
import mozilla.components.browser.session.manifest.WebAppManifest | ||
import mozilla.components.support.base.log.logger.Logger | ||
import mozilla.components.support.ktx.kotlin.toUri | ||
|
||
/** | ||
* This activity is launched by Web App shortcuts on the home screen. | ||
* | ||
* Based on the Web App Manifest (display) it will decide whether the app is launched in the browser or in a | ||
* standalone activity. | ||
*/ | ||
class WebAppLauncherActivity : AppCompatActivity() { | ||
private val logger = Logger("WebAppLauncherActivity") | ||
|
||
override fun onCreate(savedInstanceState: Bundle?) { | ||
super.onCreate(savedInstanceState) | ||
|
||
val manifest = loadManifest() | ||
|
||
routeManifest(manifest) | ||
|
||
finish() | ||
} | ||
|
||
@VisibleForTesting(otherwise = PRIVATE) | ||
internal fun routeManifest(manifest: WebAppManifest) { | ||
when (manifest.display) { | ||
WebAppManifest.DisplayMode.FULLSCREEN, WebAppManifest.DisplayMode.STANDALONE -> launchWebAppShell() | ||
|
||
// We do not implement "minimal-ui" mode. Following the Web App Manifest spec we fallback to | ||
// using "browser" in this case. | ||
WebAppManifest.DisplayMode.MINIMAL_UI -> launchBrowser(manifest) | ||
|
||
WebAppManifest.DisplayMode.BROWSER -> launchBrowser(manifest) | ||
} | ||
} | ||
|
||
@VisibleForTesting(otherwise = PRIVATE) | ||
internal fun launchBrowser(manifest: WebAppManifest) { | ||
val intent = Intent(Intent.ACTION_VIEW, manifest.startUrl.toUri()) | ||
intent.`package` = packageName | ||
|
||
try { | ||
startActivity(intent) | ||
} catch (e: ActivityNotFoundException) { | ||
logger.error("Package does not handle VIEW intent. Can't launch browser.") | ||
} | ||
} | ||
|
||
@VisibleForTesting(otherwise = PRIVATE) | ||
internal fun launchWebAppShell() { | ||
val intent = Intent() | ||
intent.action = AbstractWebAppShellActivity.INTENT_ACTION | ||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK | ||
intent.`package` = packageName | ||
|
||
try { | ||
startActivity(intent) | ||
} catch (e: ActivityNotFoundException) { | ||
logger.error("Packages does not handle AbstractWebAppShellActivity intent. Can't launch web app.", e) | ||
} | ||
} | ||
|
||
@VisibleForTesting(otherwise = PRIVATE) | ||
internal fun loadManifest(): WebAppManifest { | ||
// We do not "install" web apps yet. So there's no place we can load a manifest from yet. | ||
// https://github.com/mozilla-mobile/android-components/issues/2382 | ||
return createTestManifest() | ||
} | ||
} | ||
|
||
/** | ||
* Just a test manifest we use for testing until we save and load the actual manifests. | ||
* https://github.com/mozilla-mobile/android-components/issues/2382 | ||
*/ | ||
internal fun createTestManifest(): WebAppManifest { | ||
return WebAppManifest( | ||
name = "Demo", | ||
startUrl = "https://www.mozilla.org", | ||
display = WebAppManifest.DisplayMode.FULLSCREEN) | ||
} |
93 changes: 93 additions & 0 deletions
93
...ature/pwa/src/test/java/mozilla/components/feature/pwa/AbstractWebAppShellActivityTest.kt
This file contains 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,93 @@ | ||
/* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||
|
||
package mozilla.components.feature.pwa | ||
|
||
import android.content.pm.ActivityInfo | ||
import android.view.View | ||
import android.view.Window | ||
import android.view.WindowManager | ||
import mozilla.components.browser.session.SessionManager | ||
import mozilla.components.browser.session.manifest.WebAppManifest | ||
import mozilla.components.concept.engine.Engine | ||
import mozilla.components.support.test.mock | ||
import org.junit.Test | ||
import org.junit.runner.RunWith | ||
import org.mockito.ArgumentMatchers | ||
import org.mockito.Mockito.doReturn | ||
import org.mockito.Mockito.never | ||
import org.mockito.Mockito.spy | ||
import org.mockito.Mockito.verify | ||
import org.robolectric.RobolectricTestRunner | ||
|
||
@RunWith(RobolectricTestRunner::class) | ||
class AbstractWebAppShellActivityTest { | ||
@Test | ||
fun `applyConfiguration applies orientation`() { | ||
val activity = spy(TestWebAppShellActivity()) | ||
|
||
val manifest = WebAppManifest( | ||
name = "Test Manifest", | ||
startUrl = "/", | ||
orientation = WebAppManifest.Orientation.LANDSCAPE) | ||
|
||
activity.applyConfiguration(manifest) | ||
|
||
verify(activity).requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE | ||
} | ||
|
||
@Test | ||
fun `applyConfiguration switches to immersive mode (fullscreen display mode)`() { | ||
val decorView: View = mock() | ||
|
||
val window: Window = mock() | ||
doReturn(decorView).`when`(window).decorView | ||
|
||
val activity = spy(TestWebAppShellActivity()) | ||
doReturn(window).`when`(activity).window | ||
|
||
val manifest = WebAppManifest( | ||
name = "Test Manifest", | ||
startUrl = "/", | ||
orientation = WebAppManifest.Orientation.LANDSCAPE, | ||
display = WebAppManifest.DisplayMode.FULLSCREEN) | ||
|
||
activity.applyConfiguration(manifest) | ||
|
||
verify(window).addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) | ||
verify(decorView).systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE | ||
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | ||
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | ||
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | ||
or View.SYSTEM_UI_FLAG_FULLSCREEN | ||
or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY) | ||
} | ||
|
||
@Test | ||
fun `applyConfiguration does NOT switch to immersive mode (standalone display mode)`() { | ||
val decorView: View = mock() | ||
|
||
val window: Window = mock() | ||
doReturn(decorView).`when`(window).decorView | ||
|
||
val activity = spy(TestWebAppShellActivity()) | ||
doReturn(window).`when`(activity).window | ||
|
||
val manifest = WebAppManifest( | ||
name = "Test Manifest", | ||
startUrl = "/", | ||
orientation = WebAppManifest.Orientation.LANDSCAPE, | ||
display = WebAppManifest.DisplayMode.STANDALONE) | ||
|
||
activity.applyConfiguration(manifest) | ||
|
||
verify(window, never()).addFlags(ArgumentMatchers.anyInt()) | ||
verify(decorView, never()).systemUiVisibility = ArgumentMatchers.anyInt() | ||
} | ||
} | ||
|
||
private class TestWebAppShellActivity( | ||
override val engine: Engine = mock(), | ||
override val sessionManager: SessionManager = mock() | ||
) : AbstractWebAppShellActivity() |
Oops, something went wrong.