parent
632b64971f
commit
49b617c999
|
@ -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) {
|
||||||
|
|
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue