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
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@

package com.duckduckgo.pir.api

import com.duckduckgo.pir.api.dashboard.PirFeatureState

interface PirFeature {

/**
* Runs on the IO thread by default.
*
* @return true if the PIR beta is enabled, false otherwise
* @return [PirFeatureState] that represents the current state of the feature
*/
suspend fun isPirBetaEnabled(): Boolean
suspend fun getPirFeatureState(): PirFeatureState
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright (c) 2025 DuckDuckGo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.duckduckgo.pir.api.dashboard

/**
* Represents the state of the PIR (Private Information Retrieval) feature.
*/
enum class PirFeatureState {
/**
* The PIR feature is enabled and available for use.
*/
ENABLED,

/**
* The PIR feature is disabled and cannot be used.
*/
DISABLED,

/**
* The PIR feature is enabled but not available.
*/
NOT_AVAILABLE,
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import com.duckduckgo.feature.toggles.api.Toggle
import com.duckduckgo.feature.toggles.api.Toggle.DefaultFeatureValue
import com.duckduckgo.feature.toggles.api.Toggle.DefaultValue
import com.duckduckgo.pir.api.PirFeature
import com.duckduckgo.pir.api.dashboard.PirFeatureState
import com.duckduckgo.pir.impl.store.PirRepository
import com.squareup.anvil.annotations.ContributesBinding
import dagger.SingleInstanceIn
import kotlinx.coroutines.withContext
Expand All @@ -48,9 +50,20 @@ interface PirRemoteFeatures {
class PirRemoteFeatureImpl @Inject constructor(
private val pirRemoteFeatures: PirRemoteFeatures,
private val dispatcherProvider: DispatcherProvider,
private val pirRepository: PirRepository,
) : PirFeature {

override suspend fun isPirBetaEnabled(): Boolean = withContext(dispatcherProvider.io()) {
pirRemoteFeatures.pirBeta().isEnabled()
override suspend fun getPirFeatureState(): PirFeatureState = withContext(dispatcherProvider.io()) {
val isEnabled = pirRemoteFeatures.pirBeta().isEnabled()
if (!isEnabled) {
return@withContext PirFeatureState.DISABLED
}

val isRepositoryAvailable = pirRepository.isRepositoryAvailable()
if (!isRepositoryAvailable) {
return@withContext PirFeatureState.NOT_AVAILABLE
}

return@withContext PirFeatureState.ENABLED
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ class RealBrokerJsonUpdater @Inject constructor(
*/
override suspend fun update(): Boolean = withContext(dispatcherProvider.io()) {
return@withContext kotlin.runCatching {
if (!pirRepository.isRepositoryAvailable()) {
return@withContext false
}

confirmEtagIntegrity()
dbpService.getMainConfig(pirRepository.getCurrentMainEtag()).also {
logcat { "PIR-update: Main config result $it." }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import com.duckduckgo.pir.impl.PirRemoteFeatures
import com.duckduckgo.pir.impl.optout.PirForegroundOptOutService
import com.duckduckgo.pir.impl.scan.PirForegroundScanService
import com.duckduckgo.pir.impl.scan.PirScanScheduler
import com.duckduckgo.pir.impl.store.PirRepository
import com.duckduckgo.subscriptions.api.Product.PIR
import com.duckduckgo.subscriptions.api.SubscriptionStatus
import com.duckduckgo.subscriptions.api.Subscriptions
Expand Down Expand Up @@ -65,6 +66,7 @@ class RealPirWorkHandler @Inject constructor(
private val subscriptions: Subscriptions,
private val context: Context,
private val pirScanScheduler: PirScanScheduler,
private val pirRepository: PirRepository,
) : PirWorkHandler {

override suspend fun canRunPir(): Flow<Boolean> {
Expand All @@ -81,7 +83,7 @@ class RealPirWorkHandler @Inject constructor(
}
.distinctUntilChanged(),
) { subscriptionStatus, hasValidEntitlement ->
isPirEnabled(hasValidEntitlement, subscriptionStatus)
isPirEnabled(hasValidEntitlement, subscriptionStatus) && pirRepository.isRepositoryAvailable()
}
} else {
flowOf(false)
Expand Down
90 changes: 7 additions & 83 deletions pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/di/PirModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import com.duckduckgo.pir.impl.common.RealNativeBrokerActionHandler
import com.duckduckgo.pir.impl.common.actions.EventHandler
import com.duckduckgo.pir.impl.common.actions.PirActionsRunnerStateEngineFactory
import com.duckduckgo.pir.impl.common.actions.RealPirActionsRunnerStateEngineFactory
import com.duckduckgo.pir.impl.pixels.PirPixelSender
import com.duckduckgo.pir.impl.scripts.BrokerActionProcessor
import com.duckduckgo.pir.impl.scripts.PirMessagingInterface
import com.duckduckgo.pir.impl.scripts.RealBrokerActionProcessor
Expand All @@ -48,19 +49,9 @@ import com.duckduckgo.pir.impl.scripts.models.PirSuccessResponse.GetCaptchaInfoR
import com.duckduckgo.pir.impl.scripts.models.PirSuccessResponse.NavigateResponse
import com.duckduckgo.pir.impl.scripts.models.PirSuccessResponse.SolveCaptchaResponse
import com.duckduckgo.pir.impl.service.DbpService
import com.duckduckgo.pir.impl.store.PirDatabase
import com.duckduckgo.pir.impl.store.PirRepository
import com.duckduckgo.pir.impl.store.RealPirDataStore
import com.duckduckgo.pir.impl.store.RealPirRepository
import com.duckduckgo.pir.impl.store.db.BrokerDao
import com.duckduckgo.pir.impl.store.db.BrokerJsonDao
import com.duckduckgo.pir.impl.store.db.EmailConfirmationLogDao
import com.duckduckgo.pir.impl.store.db.ExtractedProfileDao
import com.duckduckgo.pir.impl.store.db.JobSchedulingDao
import com.duckduckgo.pir.impl.store.db.OptOutResultsDao
import com.duckduckgo.pir.impl.store.db.ScanLogDao
import com.duckduckgo.pir.impl.store.db.ScanResultsDao
import com.duckduckgo.pir.impl.store.db.UserProfileDao
import com.duckduckgo.pir.impl.store.secure.PirSecureStorageDatabaseFactory
import com.squareup.anvil.annotations.ContributesTo
import com.squareup.moshi.Moshi
Expand All @@ -70,97 +61,30 @@ import dagger.Module
import dagger.Provides
import dagger.SingleInstanceIn
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.runBlocking
import javax.inject.Named

@Module
@ContributesTo(AppScope::class)
class PirModule {

@SingleInstanceIn(AppScope::class)
@Provides
fun bindPirDatabase(
databaseFactory: PirSecureStorageDatabaseFactory,
): PirDatabase {
return runBlocking {
databaseFactory.getDatabase()
} ?: throw IllegalStateException("Failed to create PIR encrypted database")
}

@SingleInstanceIn(AppScope::class)
@Provides
fun provideBrokerJsonDao(database: PirDatabase): BrokerJsonDao {
return database.brokerJsonDao()
}

@SingleInstanceIn(AppScope::class)
@Provides
fun provideBrokerDao(database: PirDatabase): BrokerDao {
return database.brokerDao()
}

@SingleInstanceIn(AppScope::class)
@Provides
fun provideScanResultsDao(database: PirDatabase): ScanResultsDao {
return database.scanResultsDao()
}

@SingleInstanceIn(AppScope::class)
@Provides
fun provideUserProfileDao(database: PirDatabase): UserProfileDao {
return database.userProfileDao()
}

@SingleInstanceIn(AppScope::class)
@Provides
fun provideScanLogDao(database: PirDatabase): ScanLogDao {
return database.scanLogDao()
}

@SingleInstanceIn(AppScope::class)
@Provides
fun provideOptOutResultsDao(database: PirDatabase): OptOutResultsDao {
return database.optOutResultsDao()
}

@SingleInstanceIn(AppScope::class)
@Provides
fun provideJobSchedulingDao(database: PirDatabase): JobSchedulingDao {
return database.jobSchedulingDao()
}

@SingleInstanceIn(AppScope::class)
@Provides
fun provideExtractedProfileDao(database: PirDatabase): ExtractedProfileDao {
return database.extractedProfileDao()
}

@SingleInstanceIn(AppScope::class)
@Provides
fun provideEmailConfirmationLogDao(database: PirDatabase): EmailConfirmationLogDao {
return database.emailConfirmationLogDao()
}

@Provides
@SingleInstanceIn(AppScope::class)
fun providePirRepository(
sharedPreferencesProvider: SharedPreferencesProvider,
dispatcherProvider: DispatcherProvider,
brokerJsonDao: BrokerJsonDao,
brokerDao: BrokerDao,
currentTimeProvider: CurrentTimeProvider,
userProfileDao: UserProfileDao,
dbpService: DbpService,
extractedProfileDao: ExtractedProfileDao,
@AppCoroutineScope appCoroutineScope: CoroutineScope,
databaseFactory: PirSecureStorageDatabaseFactory,
pixelSender: PirPixelSender,
): PirRepository = RealPirRepository(
dispatcherProvider,
RealPirDataStore(sharedPreferencesProvider),
currentTimeProvider,
brokerJsonDao,
brokerDao,
userProfileDao,
databaseFactory,
dbpService,
extractedProfileDao,
pixelSender,
appCoroutineScope,
)

@Provides
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,11 @@ enum class PirPixel(
PIR_EMAIL_CONFIRMATION_RUN_COMPLETED(
baseName = "pir_email-confirmation_completed",
type = Count,
),

PIR_INTERNAL_SECURE_STORAGE_UNAVAILABLE(
baseName = "pir_internal_secure-storage_unavailable",
types = setOf(Count, Daily()),
), ;

constructor(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import com.duckduckgo.pir.impl.pixels.PirPixel.PIR_INTERNAL_SCAN_STATS
import com.duckduckgo.pir.impl.pixels.PirPixel.PIR_INTERNAL_SCHEDULED_SCAN_COMPLETED
import com.duckduckgo.pir.impl.pixels.PirPixel.PIR_INTERNAL_SCHEDULED_SCAN_SCHEDULED
import com.duckduckgo.pir.impl.pixels.PirPixel.PIR_INTERNAL_SCHEDULED_SCAN_STARTED
import com.duckduckgo.pir.impl.pixels.PirPixel.PIR_INTERNAL_SECURE_STORAGE_UNAVAILABLE
import com.duckduckgo.pir.impl.pixels.PirPixel.PIR_OPTOUT_STAGE_PENDING_EMAIL_CONFIRMATION
import com.squareup.anvil.annotations.ContributesBinding
import logcat.logcat
Expand Down Expand Up @@ -282,6 +283,11 @@ interface PirPixelSender {
totalFetchAttempts: Int,
totalEmailConfirmationJobs: Int,
)

/**
* Emits a pixel to signal that PIR encrypted database is unavailable.
*/
fun reportSecureStorageUnavailable()
}

@ContributesBinding(AppScope::class)
Expand Down Expand Up @@ -526,6 +532,10 @@ class RealPirPixelSender @Inject constructor(
fire(PIR_EMAIL_CONFIRMATION_RUN_COMPLETED, params)
}

override fun reportSecureStorageUnavailable() {
fire(PIR_INTERNAL_SECURE_STORAGE_UNAVAILABLE)
}

private fun fire(
pixel: PirPixel,
params: Map<String, String> = emptyMap(),
Expand Down
Loading
Loading