Commit 3827fec9 authored by MozLando's avatar MozLando
Browse files

Merge #4906



4906:  Closes #2383 - Update manifest of PWAs  r=pocmo a=NotWoods



Co-authored-by: default avatarTiger Oakes <toakes@mozilla.com>
parents e5c2f4ce aa3fe6a4
Loading
Loading
Loading
Loading
+16 −2
Original line number Diff line number Diff line
@@ -7,11 +7,15 @@ package mozilla.components.feature.pwa
import android.content.Context
import androidx.annotation.VisibleForTesting
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.withContext
import mozilla.components.concept.engine.manifest.WebAppManifest
import mozilla.components.feature.pwa.db.ManifestDatabase
import mozilla.components.feature.pwa.db.ManifestEntity

/**
 * Disk storage for [WebAppManifest]. Other components use this class to reload a saved manifest.
 */
class ManifestStorage(context: Context) {

    @VisibleForTesting
@@ -30,7 +34,7 @@ class ManifestStorage(context: Context) {
    /**
     * Save a Web App Manifest to disk.
     */
    suspend fun saveManifest(manifest: WebAppManifest) = withContext(Dispatchers.IO) {
    suspend fun saveManifest(manifest: WebAppManifest) = withContext(IO) {
        val entity = ManifestEntity(
            manifest = manifest,
            updatedAt = System.currentTimeMillis(),
@@ -39,10 +43,20 @@ class ManifestStorage(context: Context) {
        manifestDao.value.insertManifest(entity)
    }

    /**
     * Update an existing Web App Manifest on disk.
     */
    suspend fun updateManifest(manifest: WebAppManifest) = withContext(IO) {
        manifestDao.value.getManifest(manifest.startUrl)?.let { existing ->
            val update = existing.copy(manifest = manifest, updatedAt = System.currentTimeMillis())
            manifestDao.value.updateManifest(update)
        }
    }

    /**
     * Delete all manifests associated with the list of URLs.
     */
    suspend fun removeManifests(startUrls: List<String>) = withContext(Dispatchers.IO) {
    suspend fun removeManifests(startUrls: List<String>) = withContext(IO) {
        manifestDao.value.deleteManifests(startUrls)
    }
}
+14 −7
Original line number Diff line number Diff line
@@ -38,21 +38,28 @@ import mozilla.components.feature.pwa.WebAppLauncherActivity.Companion.ACTION_PW
import mozilla.components.feature.pwa.ext.installableManifest

private val pwaIconMemoryCache = IconMemoryCache()

const val SHORTCUT_CATEGORY = "mozilla.components.pwa.category.SHORTCUT"

/**
 * Helper to manage pinned shortcuts for websites.
 *
 * @param httpClient Fetch client used to load website icons.
 * @param storage Storage used to save web app manifests to disk.
 * @param supportWebApps If true, Progressive Web Apps will be pinnable.
 * If false, all web sites will be bookmark shortcuts even if they have a manifest.
 */
class WebAppShortcutManager(
    context: Context,
    httpClient: Client,
    private val storage: ManifestStorage = ManifestStorage(context),
    private val supportWebApps: Boolean = true
    private val storage: ManifestStorage,
    internal val supportWebApps: Boolean = true
) {

    @VisibleForTesting
    internal val icons = webAppIcons(context, httpClient)

    private val fallbackLabel = {
        context.getString(R.string.mozac_feature_pwa_default_shortcut_label)
    }
    private val fallbackLabel = context.getString(R.string.mozac_feature_pwa_default_shortcut_label)

    /**
     * Request to create a new shortcut on the home screen.
@@ -109,7 +116,7 @@ class WebAppShortcutManager(
        }

        val builder = ShortcutInfoCompat.Builder(context, session.url)
            .setShortLabel((overrideShortcutName ?: session.title).ifBlank(fallbackLabel))
            .setShortLabel((overrideShortcutName ?: session.title).ifBlank { fallbackLabel })
            .setIntent(shortcutIntent)

        session.icon?.let {
@@ -138,7 +145,7 @@ class WebAppShortcutManager(

        return ShortcutInfoCompat.Builder(context, manifest.startUrl)
            .setLongLabel(manifest.name)
            .setShortLabel(shortLabel.ifBlank(fallbackLabel))
            .setShortLabel(shortLabel.ifBlank { fallbackLabel })
            .setIcon(buildIconFromManifest(manifest))
            .setIntent(shortcutIntent)
            .build()
+13 −5
Original line number Diff line number Diff line
@@ -16,17 +16,25 @@ import mozilla.components.feature.pwa.ext.installableManifest
class WebAppUseCases(
    private val applicationContext: Context,
    private val sessionManager: SessionManager,
    httpClient: Client,
    private val supportWebApps: Boolean = true
    private val shortcutManager: WebAppShortcutManager
) {

    private val shortcutManager by lazy {
    @Deprecated("Pass in WebAppShortcutManager directly instead")
    constructor(
        applicationContext: Context,
        sessionManager: SessionManager,
        httpClient: Client,
        supportWebApps: Boolean = true
    ) : this(
        applicationContext,
        sessionManager,
        WebAppShortcutManager(
            applicationContext,
            httpClient,
            storage = ManifestStorage(applicationContext),
            supportWebApps = supportWebApps
        )
    }
    )

    /**
     * Checks if the launcher supports adding shortcuts.
@@ -38,7 +46,7 @@ class WebAppUseCases(
     * Checks to see if the current session can be installed as a Progressive Web App.
     */
    fun isInstallable() =
        sessionManager.selectedSession?.installableManifest() != null && supportWebApps
        sessionManager.selectedSession?.installableManifest() != null && shortcutManager.supportWebApps

    /**
     * Let the user add the selected session to the homescreen.
+5 −0
Original line number Diff line number Diff line
@@ -9,6 +9,7 @@ import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Update

/**
 * Internal DAO for accessing [ManifestEntity] instances.
@@ -23,6 +24,10 @@ internal interface ManifestDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertManifest(manifest: ManifestEntity): Long

    @WorkerThread
    @Update
    fun updateManifest(manifest: ManifestEntity)

    @WorkerThread
    @Query("DELETE FROM manifests WHERE start_url IN (:startUrls)")
    fun deleteManifests(startUrls: List<String>)
+72 −0
Original line number Diff line number Diff line
/* 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.feature

import android.content.Context
import androidx.annotation.VisibleForTesting
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager
import mozilla.components.concept.engine.manifest.WebAppManifest
import mozilla.components.feature.pwa.ManifestStorage
import mozilla.components.feature.pwa.WebAppShortcutManager
import mozilla.components.support.base.feature.LifecycleAwareFeature

/**
 * Feature used to update the existing web app manifest and web app shortcut.
 *
 * @param shortcutManager Shortcut manager used to update pinned shortcuts.
 * @param storage Manifest storage used to have updated manifests.
 * @param sessionId ID of the web app session to observe.
 * @param initialManifest Loaded manifest for the current web app.
 */
class ManifestUpdateFeature(
    private val applicationContext: Context,
    private val sessionManager: SessionManager,
    private val shortcutManager: WebAppShortcutManager,
    private val storage: ManifestStorage,
    private val sessionId: String,
    private var initialManifest: WebAppManifest
) : Session.Observer, LifecycleAwareFeature {

    private var scope: CoroutineScope? = null
    private var updateJob: Job? = null

    /**
     * When the manifest is changed, compare it to the existing manifest.
     * If it is different, update the disk and shortcut. Ignore if called with a null
     * manifest or a manifest with a different start URL.
     */
    override fun onWebAppManifestChanged(session: Session, manifest: WebAppManifest?) {
        if (manifest?.startUrl == initialManifest.startUrl && manifest != initialManifest) {
            updateJob?.cancel()
            updateJob = scope?.launch { updateStoredManifest(manifest) }
        }
    }

    /**
     * Updates the manifest on disk then updates the pinned shortcut to reflect changes.
     */
    @VisibleForTesting
    internal suspend fun updateStoredManifest(manifest: WebAppManifest) {
        storage.updateManifest(manifest)
        shortcutManager.updateShortcuts(applicationContext, listOf(manifest))
        initialManifest = manifest
    }

    override fun start() {
        scope = MainScope()
        sessionManager.findSessionById(sessionId)?.register(this)
    }

    override fun stop() {
        scope?.cancel()
        sessionManager.findSessionById(sessionId)?.unregister(this)
    }
}
Loading