Skip to content
This repository has been archived by the owner on Jan 12, 2023. It is now read-only.

Commit

Permalink
For #7249 Create a search widget
Browse files Browse the repository at this point in the history
  • Loading branch information
iorgamgabriel committed Aug 19, 2022
1 parent 63c3791 commit 813b349
Show file tree
Hide file tree
Showing 14 changed files with 344 additions and 11 deletions.
54 changes: 54 additions & 0 deletions app/metrics.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1900,3 +1900,57 @@ metrics:
metadata:
tags:
- Performance
search_widget_installed:
type: boolean
lifetime: application
description: |
Whether or not the search widget is installed
send_in_pings:
- metrics
bugs:
- https://github.com/mozilla-mobile/focus-android/issues/
data_reviews:
- https://github.com/mozilla-mobile/focus-android/pull/7474
data_sensitivity:
- interaction
notification_emails:
- [email protected]
expires: 119
metadata:
tags:
- Search

search_widget:
new_tab_button:
type: event
description: |
A user pressed anywhere from the Focus logo until the start of the
microphone icon, opening a new tab search screen.
bugs:
- https://github.com/mozilla-mobile/focus-android/issues/
data_reviews:
- https://github.com/mozilla-mobile/focus-android/pull/7474
data_sensitivity:
- interaction
notification_emails:
- [email protected]
expires: 119
metadata:
tags:
- Search
voice_button:
type: event
description: |
A user pressed the microphone icon, opening a new voice search screen.
bugs:
- https://github.com/mozilla-mobile/focus-android/issues/
data_reviews:
- https://github.com/mozilla-mobile/focus-android/pull/7474
data_sensitivity:
- interaction
notification_emails:
- [email protected]
expires: 119
metadata:
tags:
- Search
18 changes: 18 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,12 @@
<activity android:name=".activity.EraseAndOpenShortcutActivity"
android:theme="@android:style/Theme.Translucent" />

<activity
android:name=".searchwidget.VoiceSearchActivity"
android:excludeFromRecents="true"
android:taskAffinity=""
android:theme="@style/Theme.AppCompat.Translucent" />

<provider
android:authorities="${applicationId}.fileprovider"
android:name="androidx.core.content.FileProvider"
Expand Down Expand Up @@ -161,6 +167,18 @@

<meta-data android:name="android.webkit.WebView.MetricsOptOut"
android:value="true" />

<receiver
android:name=".searchwidget.SearchWidgetProvider"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/search_widget_info" />
</receiver>

</application>

</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import android.app.Activity
import android.content.Intent
import android.os.Bundle
import mozilla.components.feature.intent.ext.sanitize
import mozilla.components.feature.search.widget.BaseVoiceSearchActivity
import mozilla.components.support.utils.toSafeIntent
import org.mozilla.focus.ext.components
import org.mozilla.focus.session.IntentProcessor
Expand All @@ -32,7 +33,6 @@ class IntentReceiverActivity : Activity() {
}

val result = intentProcessor.handleIntent(this, intent, savedInstanceState)

if (result is IntentProcessor.Result.CustomTab) {
dispatchCustomTabsIntent(result.id)
} else {
Expand All @@ -43,6 +43,7 @@ class IntentReceiverActivity : Activity() {
}

private fun dispatchCustomTabsIntent(tabId: String) {

val intent = Intent(intent)

intent.setClassName(applicationContext, CustomTabActivity::class.java.name)
Expand All @@ -58,7 +59,15 @@ class IntentReceiverActivity : Activity() {
val intent = Intent(intent)
intent.setClassName(applicationContext, MainActivity::class.java.name)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP

intent.putExtra(SEARCH_WIDGET_EXTRA, intent.getBooleanExtra(SEARCH_WIDGET_EXTRA, false))
intent.putExtra(
BaseVoiceSearchActivity.SPEECH_PROCESSING,
intent.getStringExtra(BaseVoiceSearchActivity.SPEECH_PROCESSING)
)
startActivity(intent)
}

companion object {
const val SEARCH_WIDGET_EXTRA = "search_widget_extra"
}
}
58 changes: 51 additions & 7 deletions app/src/main/java/org/mozilla/focus/activity/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.isVisible
import androidx.preference.PreferenceManager
import mozilla.components.browser.state.selector.privateTabs
import mozilla.components.browser.state.state.SessionState
import mozilla.components.concept.engine.EngineView
import mozilla.components.feature.search.widget.BaseVoiceSearchActivity
import mozilla.components.lib.auth.canUseBiometricFeature
import mozilla.components.lib.crash.Crash
import mozilla.components.service.glean.private.NoExtras
Expand All @@ -32,6 +34,7 @@ import mozilla.components.support.locale.LocaleAwareAppCompatActivity
import mozilla.components.support.utils.SafeIntent
import org.mozilla.focus.GleanMetrics.AppOpened
import org.mozilla.focus.GleanMetrics.Notifications
import org.mozilla.focus.GleanMetrics.SearchWidget
import org.mozilla.focus.R
import org.mozilla.focus.appreview.AppReviewUtils
import org.mozilla.focus.databinding.ActivityMainBinding
Expand All @@ -51,6 +54,7 @@ import org.mozilla.focus.state.Screen
import org.mozilla.focus.telemetry.TelemetryWrapper
import org.mozilla.focus.telemetry.startuptelemetry.StartupPathProvider
import org.mozilla.focus.telemetry.startuptelemetry.StartupTypeTelemetry
import org.mozilla.focus.utils.SearchUtils
import org.mozilla.focus.utils.StatusBarUtils
import org.mozilla.focus.utils.SupportUtils

Expand Down Expand Up @@ -78,7 +82,6 @@ open class MainActivity : LocaleAwareAppCompatActivity() {
updateSecureWindowFlags()

super.onCreate(savedInstanceState)

_binding = ActivityMainBinding.inflate(layoutInflater)

// Checks if Activity is currently in PiP mode if launched from external intents, then exits it
Expand Down Expand Up @@ -113,12 +116,8 @@ open class MainActivity : LocaleAwareAppCompatActivity() {
}

val safeIntent = SafeIntent(intent)
val isTheFirstLaunch = settings.getAppLaunchCount() == 0
if (isTheFirstLaunch) {
setSplashScreenPreDrawListener(safeIntent)
} else {
showFirstScreen(safeIntent)
}

handleSearchWidgetNavigation(safeIntent)

if (intent.hasExtra(HomeScreen.ADD_TO_HOMESCREEN_TAG)) {
intentProcessor.handleNewIntent(this, safeIntent)
Expand All @@ -139,6 +138,28 @@ open class MainActivity : LocaleAwareAppCompatActivity() {
AppReviewUtils.showAppReview(this)
}

private fun handleSearchWidgetNavigation(safeIntent: SafeIntent) {
val voiceSearchText = safeIntent.getStringExtra(BaseVoiceSearchActivity.SPEECH_PROCESSING)
if (!voiceSearchText.isNullOrEmpty()) {
openVoiceSearchBrowser(voiceSearchText)
return
}

val searchWidgetIntent = safeIntent.getBooleanExtra(IntentReceiverActivity.SEARCH_WIDGET_EXTRA, false)
if (searchWidgetIntent) {
SearchWidget.newTabButton.record(NoExtras())
showHomeScreen()
return
}

val isTheFirstLaunch = settings.getAppLaunchCount() == 0
if (isTheFirstLaunch) {
setSplashScreenPreDrawListener(safeIntent)
} else {
showFirstScreen(safeIntent)
}
}

private fun setSplashScreenPreDrawListener(safeIntent: SafeIntent) {
val endTime = System.currentTimeMillis() + REQUEST_TIME_OUT
binding.container.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener {
Expand All @@ -155,6 +176,26 @@ open class MainActivity : LocaleAwareAppCompatActivity() {
)
}

private fun openVoiceSearchBrowser(voiceSearchText: String) {
val tabId = this.components.tabsUseCases.addTab(
url = SearchUtils.createSearchUrl(
this,
voiceSearchText
),
source = SessionState.Source.External.ActionSend(null),
searchTerms = voiceSearchText,
selectTab = true,
private = true
)
components.appStore.dispatch(AppAction.OpenTab(tabId))
lifecycle.addObserver(navigator)
}

private fun showHomeScreen() {
components.appStore.dispatch(AppAction.ShowHomeScreen)
lifecycle.addObserver(navigator)
}

private fun showFirstScreen(safeIntent: SafeIntent) {
// The performance check was added after the shouldShowFirstRun to take as much of the
// code path as possible
Expand Down Expand Up @@ -212,6 +253,7 @@ open class MainActivity : LocaleAwareAppCompatActivity() {
}

override fun onNewIntent(unsafeIntent: Intent) {

if (Crash.isCrashIntent(unsafeIntent)) {
val browserFragment = supportFragmentManager
.findFragmentByTag(BrowserFragment.FRAGMENT_TAG) as BrowserFragment?
Expand All @@ -222,6 +264,8 @@ open class MainActivity : LocaleAwareAppCompatActivity() {
startupPathProvider.onIntentReceived(intent)
val intent = SafeIntent(unsafeIntent)

handleSearchWidgetNavigation(intent)

if (intent.dataString.equals(SupportUtils.OPEN_WITH_DEFAULT_BROWSER_URL)) {
components.appStore.dispatch(
AppAction.OpenSettings(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/* 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 org.mozilla.focus.searchwidget

import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import androidx.annotation.VisibleForTesting
import mozilla.components.feature.search.widget.AppSearchWidgetProvider
import mozilla.components.feature.search.widget.BaseVoiceSearchActivity
import mozilla.components.feature.search.widget.SearchWidgetConfig
import mozilla.components.support.utils.PendingIntentUtils
import org.mozilla.focus.R
import org.mozilla.focus.activity.IntentReceiverActivity
import org.mozilla.focus.ext.components

class SearchWidgetProvider : AppSearchWidgetProvider() {

override fun onEnabled(context: Context) {
context.components.settings.addSearchWidgetInstalled(1)
}

override fun onDeleted(context: Context, appWidgetIds: IntArray) {
context.components.settings.addSearchWidgetInstalled(-appWidgetIds.size)
}

override val config: SearchWidgetConfig =
SearchWidgetConfig(
searchWidgetIconResource = R.drawable.ic_splash_screen,
searchWidgetMicrophoneResource = R.drawable.mozac_ic_microphone,
appName = R.string.app_name
)

override fun createTextSearchIntent(context: Context): PendingIntent {
val textSearchIntent = Intent(context, IntentReceiverActivity::class.java)
.apply {
this.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
this.putExtra(IntentReceiverActivity.SEARCH_WIDGET_EXTRA, true)
}
return PendingIntent.getActivity(
context,
REQUEST_CODE_NEW_TAB,
textSearchIntent,
PendingIntentUtils.defaultFlags or
PendingIntent.FLAG_UPDATE_CURRENT
)
}

override fun shouldShowVoiceSearch(context: Context): Boolean {
return true
}

override fun voiceSearchActivity(): Class<out BaseVoiceSearchActivity> {
return VoiceSearchActivity::class.java
}

companion object {
@VisibleForTesting
const val REQUEST_CODE_NEW_TAB = 0
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/* 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 org.mozilla.focus.searchwidget

import android.content.Intent
import mozilla.components.feature.search.widget.BaseVoiceSearchActivity
import mozilla.components.support.locale.LocaleManager
import mozilla.components.support.locale.LocaleManager.getCurrentLocale
import mozilla.telemetry.glean.private.NoExtras
import org.mozilla.focus.GleanMetrics.SearchWidget
import org.mozilla.focus.activity.IntentReceiverActivity
import java.util.Locale

class VoiceSearchActivity : BaseVoiceSearchActivity() {

override fun getCurrentLocale(): Locale {
return getCurrentLocale(this)
?: LocaleManager.getSystemDefault()
}

override fun onSpeechRecognitionEnded(spokenText: String) {
val intent = Intent(this, IntentReceiverActivity::class.java)
intent.putExtra(SPEECH_PROCESSING, spokenText)
startActivity(intent)
}

override fun onSpeechRecognitionStarted() {
SearchWidget.voiceButton.record(NoExtras())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import org.mozilla.focus.Components
import org.mozilla.focus.GleanMetrics.Browser
import org.mozilla.focus.GleanMetrics.GleanBuildInfo
import org.mozilla.focus.GleanMetrics.LegacyIds
import org.mozilla.focus.GleanMetrics.Metrics
import org.mozilla.focus.GleanMetrics.MozillaProducts
import org.mozilla.focus.GleanMetrics.Pings
import org.mozilla.focus.GleanMetrics.Preferences
Expand Down Expand Up @@ -75,7 +76,7 @@ class GleanMetricsService(context: Context) : MetricsService {
GlobalScope.launch(IO) {

// Wait for preferences to be collected before we send the activation ping.
collectPrefMetrics(components, settings, context).await()
collectPrefMetricsAsync(components, settings, context).await()

// Set the client ID in Glean as part of the deletion-request.
LegacyIds.clientId.set(UUID.fromString(TelemetryWrapper.clientId))
Expand All @@ -90,7 +91,7 @@ class GleanMetricsService(context: Context) : MetricsService {
}
}

private fun collectPrefMetrics(
private fun collectPrefMetricsAsync(
components: Components,
settings: Settings,
context: Context
Expand All @@ -100,6 +101,8 @@ class GleanMetricsService(context: Context) : MetricsService {
val isFenixDefaultBrowser = FenixProductDetector.isFenixDefaultBrowser(installedBrowsers.defaultBrowser)
val isFocusDefaultBrowser = installedBrowsers.isDefaultBrowser

Metrics.searchWidgetInstalled.set(settings.searchWidgetInstalled)

Browser.isDefault.set(isFocusDefaultBrowser)
Browser.localeOverride.set(components.store.state.locale?.displayName ?: "none")
val shortcutsOnHomeNumber = components.topSitesStorage.getTopSites(
Expand Down
Loading

0 comments on commit 813b349

Please sign in to comment.