1
0
Fork 0

For #9100 - Private browsing notification fixes

Co-authored-by: Seef <Saif Dara>
master
Mihai Branescu 2020-06-25 04:35:11 +03:00 committed by GitHub
parent 632b64971f
commit 49b617c999
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 80 additions and 20 deletions

View File

@ -43,7 +43,6 @@ import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.perf.StartupTimeline import org.mozilla.fenix.perf.StartupTimeline
import org.mozilla.fenix.push.PushFxaIntegration import org.mozilla.fenix.push.PushFxaIntegration
import org.mozilla.fenix.push.WebPushEngineIntegration import org.mozilla.fenix.push.WebPushEngineIntegration
import org.mozilla.fenix.session.NotificationSessionObserver
import org.mozilla.fenix.session.PerformanceActivityLifecycleCallbacks import org.mozilla.fenix.session.PerformanceActivityLifecycleCallbacks
import org.mozilla.fenix.session.VisibilityLifecycleCallback import org.mozilla.fenix.session.VisibilityLifecycleCallback
import org.mozilla.fenix.utils.BrowsersCache import org.mozilla.fenix.utils.BrowsersCache
@ -157,9 +156,6 @@ open class FenixApplication : LocaleAwareApplication() {
visibilityLifecycleCallback = VisibilityLifecycleCallback(getSystemService()) visibilityLifecycleCallback = VisibilityLifecycleCallback(getSystemService())
registerActivityLifecycleCallbacks(visibilityLifecycleCallback) registerActivityLifecycleCallbacks(visibilityLifecycleCallback)
val privateNotificationObserver = NotificationSessionObserver(this)
privateNotificationObserver.start()
// Storage maintenance disabled, for now, as it was interfering with background migrations. // Storage maintenance disabled, for now, as it was interfering with background migrations.
// See https://github.com/mozilla-mobile/fenix/issues/7227 for context. // See https://github.com/mozilla-mobile/fenix/issues/7227 for context.
// if ((System.currentTimeMillis() - settings().lastPlacesStorageMaintenance) > ONE_DAY_MILLIS) { // if ((System.currentTimeMillis() - settings().lastPlacesStorageMaintenance) > ONE_DAY_MILLIS) {

View File

@ -74,6 +74,7 @@ import org.mozilla.fenix.library.history.HistoryFragmentDirections
import org.mozilla.fenix.perf.Performance import org.mozilla.fenix.perf.Performance
import org.mozilla.fenix.perf.StartupTimeline import org.mozilla.fenix.perf.StartupTimeline
import org.mozilla.fenix.search.SearchFragmentDirections import org.mozilla.fenix.search.SearchFragmentDirections
import org.mozilla.fenix.session.NotificationSessionObserver
import org.mozilla.fenix.settings.SettingsFragmentDirections import org.mozilla.fenix.settings.SettingsFragmentDirections
import org.mozilla.fenix.settings.TrackingProtectionFragmentDirections import org.mozilla.fenix.settings.TrackingProtectionFragmentDirections
import org.mozilla.fenix.settings.about.AboutFragmentDirections import org.mozilla.fenix.settings.about.AboutFragmentDirections
@ -154,6 +155,10 @@ open class HomeActivity : LocaleAwareAppCompatActivity() {
sessionObserver = UriOpenedObserver(this) sessionObserver = UriOpenedObserver(this)
checkPrivateShortcutEntryPoint(intent)
val privateNotificationObserver = NotificationSessionObserver(this)
privateNotificationObserver.start()
if (isActivityColdStarted(intent, savedInstanceState)) { if (isActivityColdStarted(intent, savedInstanceState)) {
externalSourceIntentProcessors.any { it.process(intent, navHost.navController, this.intent) } externalSourceIntentProcessors.any { it.process(intent, navHost.navController, this.intent) }
} }
@ -176,6 +181,11 @@ open class HomeActivity : LocaleAwareAppCompatActivity() {
StartupTimeline.homeActivityLifecycleObserver StartupTimeline.homeActivityLifecycleObserver
) )
StartupTimeline.onActivityCreateEndHome(this) StartupTimeline.onActivityCreateEndHome(this)
if (shouldAddToRecentsScreen(intent)) {
intent.removeExtra(START_IN_RECENTS_SCREEN)
moveTaskToBack(true)
}
} }
@CallSuper @CallSuper
@ -313,6 +323,30 @@ open class HomeActivity : LocaleAwareAppCompatActivity() {
return settings().lastKnownMode return settings().lastKnownMode
} }
/**
* Determines whether the activity should be pushed to be backstack (i.e., 'minimized' to the recents
* screen) upon starting.
* @param intent - The intent that started this activity. Is checked for having the 'START_IN_RECENTS_SCREEN'-extra.
* @return true if the activity should be started and pushed to the recents screen, false otherwise.
*/
private fun shouldAddToRecentsScreen(intent: Intent?): Boolean {
intent?.toSafeIntent()?.let {
return it.getBooleanExtra(START_IN_RECENTS_SCREEN, false)
}
return false
}
private fun checkPrivateShortcutEntryPoint(intent: Intent) {
if (intent.hasExtra(OPEN_TO_SEARCH) &&
(intent.getStringExtra(OPEN_TO_SEARCH) ==
StartSearchIntentProcessor.STATIC_SHORTCUT_NEW_PRIVATE_TAB ||
intent.getStringExtra(OPEN_TO_SEARCH) ==
StartSearchIntentProcessor.PRIVATE_BROWSING_PINNED_SHORTCUT)
) {
NotificationSessionObserver.isStartedFromPrivateShortcut = true
}
}
private fun setupThemeAndBrowsingMode(mode: BrowsingMode) { private fun setupThemeAndBrowsingMode(mode: BrowsingMode) {
settings().lastKnownMode = mode settings().lastKnownMode = mode
browsingModeManager = createBrowsingModeManager(mode) browsingModeManager = createBrowsingModeManager(mode)
@ -499,5 +533,6 @@ open class HomeActivity : LocaleAwareAppCompatActivity() {
const val EXTRA_DELETE_PRIVATE_TABS = "notification_delete_and_open" const val EXTRA_DELETE_PRIVATE_TABS = "notification_delete_and_open"
const val EXTRA_OPENED_FROM_NOTIFICATION = "notification_open" const val EXTRA_OPENED_FROM_NOTIFICATION = "notification_open"
const val delay = 5000L const val delay = 5000L
const val START_IN_RECENTS_SCREEN = "start_in_recents_screen"
} }
} }

View File

@ -34,7 +34,7 @@ class NotificationSessionObserver(
.ifChanged() .ifChanged()
.collect { hasPrivateTabs -> .collect { hasPrivateTabs ->
if (hasPrivateTabs) { if (hasPrivateTabs) {
notificationService.start(context) notificationService.start(context, isStartedFromPrivateShortcut)
started = true started = true
} else if (started) { } else if (started) {
notificationService.stop(context) notificationService.stop(context)
@ -47,4 +47,8 @@ class NotificationSessionObserver(
fun stop() { fun stop() {
scope?.cancel() scope?.cancel()
} }
companion object {
var isStartedFromPrivateShortcut = false
}
} }

View File

@ -37,32 +37,41 @@ import org.mozilla.fenix.ext.sessionsOfType
*/ */
class SessionNotificationService : Service() { class SessionNotificationService : Service() {
private var isStartedFromPrivateShortcut: Boolean = false
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
val action = intent.action ?: return Service.START_NOT_STICKY val action = intent.action ?: return START_NOT_STICKY
when (action) { when (action) {
ACTION_START -> { ACTION_START -> {
isStartedFromPrivateShortcut = intent.getBooleanExtra(STARTED_FROM_PRIVATE_SHORTCUT, false)
createNotificationChannelIfNeeded() createNotificationChannelIfNeeded()
startForeground(NOTIFICATION_ID, buildNotification()) startForeground(NOTIFICATION_ID, buildNotification())
} }
ACTION_ERASE -> { ACTION_ERASE -> {
metrics.track(Event.PrivateBrowsingNotificationTapped) metrics.track(Event.PrivateBrowsingNotificationTapped)
components.core.sessionManager.removeAndCloseAllPrivateSessions()
if (!VisibilityLifecycleCallback.finishAndRemoveTaskIfInBackground(this)) { val homeScreenIntent = Intent(this, HomeActivity::class.java)
startActivity( val intentFlags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
Intent(this, HomeActivity::class.java).apply { homeScreenIntent.apply {
this.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK setFlags(intentFlags)
} putExtra(HomeActivity.PRIVATE_BROWSING_MODE, isStartedFromPrivateShortcut)
)
} }
if (VisibilityLifecycleCallback.finishAndRemoveTaskIfInBackground(this)) {
// Set start mode to be in background (recents screen)
homeScreenIntent.apply {
putExtra(HomeActivity.START_IN_RECENTS_SCREEN, true)
}
}
startActivity(homeScreenIntent)
components.core.sessionManager.removeAndCloseAllPrivateSessions()
} }
else -> throw IllegalStateException("Unknown intent: $intent") else -> throw IllegalStateException("Unknown intent: $intent")
} }
return Service.START_NOT_STICKY return START_NOT_STICKY
} }
override fun onTaskRemoved(rootIntent: Intent) { override fun onTaskRemoved(rootIntent: Intent) {
@ -125,13 +134,18 @@ class SessionNotificationService : Service() {
companion object { companion object {
private const val NOTIFICATION_ID = 83 private const val NOTIFICATION_ID = 83
private const val NOTIFICATION_CHANNEL_ID = "browsing-session" private const val NOTIFICATION_CHANNEL_ID = "browsing-session"
private const val STARTED_FROM_PRIVATE_SHORTCUT = "STARTED_FROM_PRIVATE_SHORTCUT"
private const val ACTION_START = "start" private const val ACTION_START = "start"
private const val ACTION_ERASE = "erase" private const val ACTION_ERASE = "erase"
internal fun start(context: Context) { internal fun start(
context: Context,
startedFromPrivateShortcut: Boolean
) {
val intent = Intent(context, SessionNotificationService::class.java) val intent = Intent(context, SessionNotificationService::class.java)
intent.action = ACTION_START intent.action = ACTION_START
intent.putExtra(STARTED_FROM_PRIVATE_SHORTCUT, startedFromPrivateShortcut)
// From Focus #2901: The application is crashing due to the service not calling `startForeground` // From Focus #2901: The application is crashing due to the service not calling `startForeground`
// before it times out. This is a speculative fix to decrease the time between these two // before it times out. This is a speculative fix to decrease the time between these two

View File

@ -25,6 +25,13 @@ class VisibilityLifecycleCallback(private val activityManager: ActivityManager?)
*/ */
private var activitiesInStartedState: Int = 0 private var activitiesInStartedState: Int = 0
/**
* Finishes and removes the list of AppTasks only if the application is in the background.
* The application is considered to be in the background if it has at least 1 Activity in the
* started state
* @return True if application is in background (also finishes and removes all AppTasks),
* false otherwise
*/
private fun finishAndRemoveTaskIfInBackground(): Boolean { private fun finishAndRemoveTaskIfInBackground(): Boolean {
if (activitiesInStartedState == 0) { if (activitiesInStartedState == 0) {
activityManager?.let { activityManager?.let {
@ -59,6 +66,9 @@ class VisibilityLifecycleCallback(private val activityManager: ActivityManager?)
/** /**
* If all activities of this app are in the background then finish and remove all tasks. After * If all activities of this app are in the background then finish and remove all tasks. After
* that the app won't show up in "recent apps" anymore. * that the app won't show up in "recent apps" anymore.
*
* @return True if application is in background (and consequently, finishes and removes all tasks),
* false otherwise.
*/ */
internal fun finishAndRemoveTaskIfInBackground(context: Context): Boolean { internal fun finishAndRemoveTaskIfInBackground(context: Context): Boolean {
return (context.applicationContext as FenixApplication) return (context.applicationContext as FenixApplication)

View File

@ -35,6 +35,7 @@ class NotificationSessionObserverTest {
store = BrowserStore() store = BrowserStore()
every { context.components.core.store } returns store every { context.components.core.store } returns store
observer = NotificationSessionObserver(context, notificationService) observer = NotificationSessionObserver(context, notificationService)
NotificationSessionObserver.isStartedFromPrivateShortcut = false
} }
@Test @Test
@ -44,7 +45,7 @@ class NotificationSessionObserverTest {
store.dispatch(TabListAction.AddTabAction(privateSession)).join() store.dispatch(TabListAction.AddTabAction(privateSession)).join()
observer.start() observer.start()
verify(exactly = 1) { notificationService.start(context) } verify(exactly = 1) { notificationService.start(context, false) }
confirmVerified(notificationService) confirmVerified(notificationService)
} }
@ -57,10 +58,10 @@ class NotificationSessionObserverTest {
verify { notificationService wasNot Called } verify { notificationService wasNot Called }
store.dispatch(TabListAction.AddTabAction(normalSession)).join() store.dispatch(TabListAction.AddTabAction(normalSession)).join()
verify(exactly = 0) { notificationService.start(context) } verify(exactly = 0) { notificationService.start(context, false) }
store.dispatch(CustomTabListAction.AddCustomTabAction(customSession)).join() store.dispatch(CustomTabListAction.AddCustomTabAction(customSession)).join()
verify(exactly = 0) { notificationService.start(context) } verify(exactly = 0) { notificationService.start(context, false) }
} }
@Test @Test
@ -74,9 +75,9 @@ class NotificationSessionObserverTest {
verify { notificationService wasNot Called } verify { notificationService wasNot Called }
store.dispatch(CustomTabListAction.AddCustomTabAction(privateCustomSession)).join() store.dispatch(CustomTabListAction.AddCustomTabAction(privateCustomSession)).join()
verify(exactly = 0) { notificationService.start(context) } verify(exactly = 0) { notificationService.start(context, false) }
store.dispatch(CustomTabListAction.AddCustomTabAction(customSession)).join() store.dispatch(CustomTabListAction.AddCustomTabAction(customSession)).join()
verify(exactly = 0) { notificationService.start(context) } verify(exactly = 0) { notificationService.start(context, false) }
} }
} }