diff --git a/lawnchair/res/drawable/ic_smartspace_preferences.xml b/lawnchair/res/drawable/ic_smartspace_preferences.xml
new file mode 100644
index 0000000000..bcf14ef1d4
--- /dev/null
+++ b/lawnchair/res/drawable/ic_smartspace_preferences.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/lawnchair/res/layout/search_container_all_apps.xml b/lawnchair/res/layout/search_container_all_apps.xml
new file mode 100644
index 0000000000..729a87d990
--- /dev/null
+++ b/lawnchair/res/layout/search_container_all_apps.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
diff --git a/lawnchair/res/layout/search_container_workspace.xml b/lawnchair/res/layout/search_container_workspace.xml
new file mode 100644
index 0000000000..31342acced
--- /dev/null
+++ b/lawnchair/res/layout/search_container_workspace.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/lawnchair/res/layout/smart_space_date_view.xml b/lawnchair/res/layout/smart_space_date_view.xml
new file mode 100644
index 0000000000..75f93b5d24
--- /dev/null
+++ b/lawnchair/res/layout/smart_space_date_view.xml
@@ -0,0 +1,27 @@
+
+
+
+
diff --git a/lawnchair/res/values/dimens.xml b/lawnchair/res/values/dimens.xml
new file mode 100644
index 0000000000..9ce30883b2
--- /dev/null
+++ b/lawnchair/res/values/dimens.xml
@@ -0,0 +1,9 @@
+
+
+ 70dp
+ 0dp
+ 72dp
+
+ 24dp
+ 56dp
+
\ No newline at end of file
diff --git a/lawnchair/res/values/strings.xml b/lawnchair/res/values/strings.xml
index 95e7ad3466..508cce7894 100644
--- a/lawnchair/res/values/strings.xml
+++ b/lawnchair/res/values/strings.xml
@@ -36,4 +36,6 @@
Make Colored Backgrounds
For generated Adaptive icons.
Notification Dots
+
+ Preferences
diff --git a/lawnchair/src/ch/deletescape/nexuslauncher/HotseatQsb.kt b/lawnchair/src/ch/deletescape/nexuslauncher/HotseatQsb.kt
new file mode 100644
index 0000000000..a7888963e0
--- /dev/null
+++ b/lawnchair/src/ch/deletescape/nexuslauncher/HotseatQsb.kt
@@ -0,0 +1,162 @@
+package ch.deletescape.nexuslauncher
+
+import android.content.Context
+import android.graphics.Rect
+import android.text.Selection
+import android.text.Spannable
+import android.text.SpannableString
+import android.text.SpannableStringBuilder
+import android.text.method.TextKeyListener
+import android.util.AttributeSet
+import android.view.KeyEvent
+import android.view.View
+import android.view.animation.Interpolator
+import android.widget.EditText
+import com.android.launcher3.*
+import com.android.launcher3.allapps.AllAppsContainerView
+import com.android.launcher3.allapps.AllAppsStore
+import com.android.launcher3.allapps.AlphabeticalAppsList
+import com.android.launcher3.allapps.SearchUiManager
+import com.android.launcher3.allapps.search.AllAppsSearchBarController
+import com.android.launcher3.allapps.search.DefaultAppSearchAlgorithm
+import com.android.launcher3.anim.Interpolators
+import com.android.launcher3.anim.PropertySetter
+import com.android.launcher3.graphics.TintedDrawableSpan
+import com.android.launcher3.qsb.QsbContainerView
+import com.android.launcher3.util.ComponentKey
+import com.android.launcher3.views.ActivityContext
+import java.util.*
+
+class HotseatQsb @JvmOverloads constructor(context: Context?, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
+ QsbContainerView(context, attrs, defStyleAttr), Insettable, SearchUiManager, AllAppsSearchBarController.Callbacks, AllAppsStore.OnUpdateListener {
+ private val mActivity: ActivityContext = ActivityContext.lookupContext(context)
+ private val mFixedTranslationY: Int = resources.getDimensionPixelSize(R.dimen.search_widget_hotseat_height) / 2
+ private val mMarginTopAdjusting: Int = resources.getDimensionPixelSize(R.dimen.search_widget_top_shift)
+ private val mSearchBarController: AllAppsSearchBarController = AllAppsSearchBarController()
+ private val mSearchQueryBuilder: SpannableStringBuilder = SpannableStringBuilder()
+ private var mApps: AlphabeticalAppsList? = null
+ private var mAppsView: AllAppsContainerView? = null
+ private var mSearchWrapperView: View? = null
+ private var mFallbackSearchView: ExtendedEditText? = null
+
+ override fun onFinishInflate() {
+ super.onFinishInflate()
+ mFallbackSearchView = findViewById(R.id.fallback_search_view)
+ mSearchWrapperView = findViewById(R.id.search_wrapper_view)
+ val spanned = SpannableString(" " + mFallbackSearchView?.hint)
+ spanned.setSpan(TintedDrawableSpan(context, R.drawable.ic_allapps_search),
+ 0, 1, Spannable.SPAN_EXCLUSIVE_INCLUSIVE)
+ mFallbackSearchView?.hint = spanned
+ }
+
+ override fun onAttachedToWindow() {
+ super.onAttachedToWindow()
+ mAppsView?.appsStore?.addUpdateListener(this)
+ }
+
+ override fun onDetachedFromWindow() {
+ super.onDetachedFromWindow()
+ mAppsView?.appsStore?.removeUpdateListener(this)
+ }
+
+ override fun setInsets(insets: Rect) {
+ visibility = if (mActivity.deviceProfile.isVerticalBarLayout) View.GONE else View.VISIBLE
+ val mlp: MarginLayoutParams = layoutParams as MarginLayoutParams
+ mlp.topMargin = (-mFixedTranslationY).coerceAtLeast(insets.top - mMarginTopAdjusting)
+ val padding: Rect = mActivity.deviceProfile.hotseatLayoutPadding
+ setPaddingUnchecked(padding.left, 0, padding.right, 0)
+ requestLayout()
+ }
+
+ override fun initialize(appsView: AllAppsContainerView) {
+ mApps = appsView.apps
+ mAppsView = appsView
+ mSearchBarController.initialize(DefaultAppSearchAlgorithm(mApps?.apps),
+ mFallbackSearchView, Launcher.cast(mActivity), this)
+ }
+
+ override fun onAppsUpdated() {
+ mSearchBarController.refreshSearchResult()
+ }
+
+ override fun resetSearch() {
+ mSearchBarController.reset()
+ }
+
+ override fun preDispatchKeyEvent(event: KeyEvent) {
+ if (!mSearchBarController.isSearchFieldFocused &&
+ event.action == KeyEvent.ACTION_DOWN) {
+ val unicodeChar = event.unicodeChar
+ val isKeyNotWhitespace = unicodeChar > 0 &&
+ !Character.isWhitespace(unicodeChar) && !Character.isSpaceChar(unicodeChar)
+ if (isKeyNotWhitespace) {
+ val gotKey: Boolean = TextKeyListener.getInstance().onKeyDown(this, mSearchQueryBuilder,
+ event.keyCode, event)
+ if (gotKey && mSearchQueryBuilder.isNotEmpty()) {
+ mSearchBarController.focusSearchField()
+ }
+ }
+ }
+ }
+
+ override fun onSearchResult(query: String, apps: ArrayList) {
+ mApps?.setOrderedFilter(apps)
+ notifyResultChanged()
+ mAppsView?.setLastSearchQuery(query)
+ }
+
+ override fun clearSearchResult() {
+ if (mApps?.setOrderedFilter(null) == true) {
+ notifyResultChanged()
+ }
+ mSearchQueryBuilder.clear()
+ mSearchQueryBuilder.clearSpans()
+ Selection.setSelection(mSearchQueryBuilder, 0)
+ mAppsView?.onClearSearchResult()
+ }
+
+ private fun notifyResultChanged() {
+ mAppsView?.onSearchResultsChanged()
+ }
+
+ override fun getScrollRangeDelta(insets: Rect): Float {
+ return if (mActivity.deviceProfile.isVerticalBarLayout) {
+ 0.0f
+ } else {
+ val dp: DeviceProfile = mActivity.deviceProfile
+ val percentageOfAvailSpaceFromBottom = 0.45f
+ val center = ((dp.hotseatBarSizePx - dp.hotseatCellHeightPx
+ - layoutParams.height - insets.bottom) * percentageOfAvailSpaceFromBottom).toInt()
+ val bottomMargin = insets.bottom + center
+ val topMargin = (-mFixedTranslationY).coerceAtLeast(insets.top - mMarginTopAdjusting)
+ val myBot: Int = layoutParams.height + topMargin + mFixedTranslationY
+ (bottomMargin + myBot).toFloat()
+ }
+ }
+
+ override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
+ super.onLayout(changed, left, top, right, bottom)
+ offsetTopAndBottom(mFixedTranslationY)
+ }
+
+ override fun setContentVisibility(visibleElements: Int, setter: PropertySetter,
+ interpolator: Interpolator) {
+ val showAllAppsMode = visibleElements and LauncherState.ALL_APPS_CONTENT != 0
+ setter.setViewAlpha(mSearchWrapperView, if (showAllAppsMode) 0f else 1f, Interpolators.LINEAR)
+ setter.setViewAlpha(mFallbackSearchView, if (showAllAppsMode) 1f else 0f, Interpolators.LINEAR)
+ }
+
+ override fun setTextSearchEnabled(isEnabled: Boolean): EditText? {
+ return mFallbackSearchView
+ }
+
+ class HotseatQsbFragment : QsbFragment() {
+ override fun isQsbEnabled(): Boolean {
+ return true
+ }
+ }
+
+ init {
+ Selection.setSelection(mSearchQueryBuilder, 0)
+ }
+}
diff --git a/lawnchair/src/ch/deletescape/nexuslauncher/SmartSpaceHostView.kt b/lawnchair/src/ch/deletescape/nexuslauncher/SmartSpaceHostView.kt
new file mode 100644
index 0000000000..3631a225cd
--- /dev/null
+++ b/lawnchair/src/ch/deletescape/nexuslauncher/SmartSpaceHostView.kt
@@ -0,0 +1,119 @@
+package ch.deletescape.nexuslauncher
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.content.Intent
+import android.graphics.Rect
+import android.graphics.RectF
+import android.view.HapticFeedbackConstants
+import android.view.MotionEvent
+import android.view.View
+import android.view.View.OnLongClickListener
+import android.view.ViewGroup
+import com.android.launcher3.CheckLongPressHelper
+import com.android.launcher3.Launcher
+import com.android.launcher3.R
+import com.android.launcher3.logging.StatsLogManager.EventEnum
+import com.android.launcher3.qsb.QsbWidgetHostView
+import com.android.launcher3.views.BaseDragLayer.TouchCompleteListener
+import com.android.launcher3.views.OptionsPopupView
+import com.android.launcher3.views.OptionsPopupView.OptionItem
+
+class SmartSpaceHostView(context: Context?) : QsbWidgetHostView(context), OnLongClickListener, TouchCompleteListener {
+ private val mLauncher: Launcher = Launcher.getLauncher(context)
+ private val mLongPressHelper: CheckLongPressHelper = CheckLongPressHelper(this, this)
+
+ override fun getErrorView(): View {
+ return SmartspaceQsb.getDateView(this)
+ }
+
+ override fun onLongClick(view: View): Boolean {
+ if (!hasSettings(view.context)) {
+ return false
+ }
+ performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
+ val pos = Rect()
+ mLauncher.dragLayer.getDescendantRectRelativeToSelf(this, pos)
+ val centerPos = RectF()
+ centerPos.right = pos.exactCenterX()
+ centerPos.left = centerPos.right
+ centerPos.top = 0f
+ centerPos.bottom = pos.bottom.toFloat()
+ centerPos.bottom = findBottomRecur(this, pos.top, pos).toFloat().coerceAtMost(centerPos.bottom)
+ val item = OptionItem(R.string.smartspace_preferences,
+ R.drawable.ic_smartspace_preferences, NexusLauncherEnum.SMARTSPACE_TAP_OR_LONGPRESS, { v: View -> openSettings(v) })
+ OptionsPopupView.show(mLauncher, centerPos, listOf(item))
+ return true
+ }
+
+ private fun findBottomRecur(view: View, max: Int, tempRect: Rect): Int {
+ var ret = max
+ if (view.visibility != VISIBLE) {
+ return ret
+ }
+ if (view is ViewGroup) {
+ for (i in view.childCount - 1 downTo 0) {
+ ret = findBottomRecur(view.getChildAt(i), ret, tempRect).coerceAtLeast(ret)
+ }
+ }
+ if (!view.willNotDraw()) {
+ mLauncher.dragLayer.getDescendantRectRelativeToSelf(view, tempRect)
+ return ret.coerceAtLeast(tempRect.bottom)
+ }
+ return ret
+ }
+
+ override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
+ mLongPressHelper.onTouchEvent(ev)
+ return mLongPressHelper.hasPerformedLongPress()
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ override fun onTouchEvent(ev: MotionEvent): Boolean {
+ mLongPressHelper.onTouchEvent(ev)
+ return true
+ }
+
+ override fun cancelLongPress() {
+ super.cancelLongPress()
+ mLongPressHelper.cancelLongPress()
+ }
+
+ override fun onTouchComplete() {
+ if (!mLongPressHelper.hasPerformedLongPress()) {
+ mLongPressHelper.cancelLongPress()
+ }
+ }
+
+ private fun openSettings(v: View): Boolean {
+ v.context.startActivity(createSettingsIntent())
+ return true
+ }
+
+ companion object {
+ private const val SETTINGS_INTENT_ACTION = "com.google.android.apps.gsa.smartspace.SETTINGS"
+ fun hasSettings(context: Context): Boolean {
+ val info = context.packageManager
+ .resolveActivity(createSettingsIntent(), 0)
+ return info != null
+ }
+
+ fun createSettingsIntent(): Intent {
+ return Intent(SETTINGS_INTENT_ACTION)
+ .setPackage(SmartspaceQsb.WIDGET_PACKAGE_NAME)
+ .setFlags(Intent.FLAG_RECEIVER_FOREGROUND
+ or Intent.FLAG_ACTIVITY_NO_HISTORY
+ or Intent.FLAG_ACTIVITY_NEW_TASK
+ or Intent.FLAG_ACTIVITY_NEW_DOCUMENT)
+ }
+ }
+
+}
+
+enum class NexusLauncherEnum(private val mId: Int) : EventEnum {
+ SMARTSPACE_TAP_OR_LONGPRESS(520);
+
+ override fun getId(): Int {
+ return mId
+ }
+}
diff --git a/lawnchair/src/ch/deletescape/nexuslauncher/SmartspaceQsb.kt b/lawnchair/src/ch/deletescape/nexuslauncher/SmartspaceQsb.kt
new file mode 100644
index 0000000000..0e1fc491af
--- /dev/null
+++ b/lawnchair/src/ch/deletescape/nexuslauncher/SmartspaceQsb.kt
@@ -0,0 +1,62 @@
+package ch.deletescape.nexuslauncher
+
+import android.annotation.SuppressLint
+import android.appwidget.AppWidgetManager
+import android.appwidget.AppWidgetProviderInfo
+import android.content.Context
+import android.os.Bundle
+import android.os.Process
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import com.android.launcher3.R
+import com.android.launcher3.qsb.QsbContainerView
+
+class SmartspaceQsb @JvmOverloads constructor(context: Context?, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : QsbContainerView(context, attrs, defStyleAttr) {
+ class SmartSpaceFragment : QsbFragment() {
+ override fun createHost(): QsbWidgetHost {
+ return QsbWidgetHost(context, SMART_SPACE_WIDGET_HOST_ID
+ ) { c: Context? -> SmartSpaceHostView(c) }
+ }
+
+ @SuppressLint("NewApi")
+ override fun getSearchWidgetProvider(): AppWidgetProviderInfo? {
+ for (info in AppWidgetManager.getInstance(context)
+ .getInstalledProvidersForPackage(WIDGET_PACKAGE_NAME, Process.myUserHandle())) {
+ if (WIDGET_CLASS_NAME == info.provider.className) {
+ return info
+ }
+ }
+ return null
+ }
+
+ override fun getDefaultView(container: ViewGroup, showSetupIcon: Boolean): View {
+ return getDateView(container)
+ }
+
+ override fun createBindOptions(): Bundle {
+ val opts = super.createBindOptions()
+ opts.putString("attached-launcher-identifier", context.packageName)
+ opts.putBoolean("com.google.android.apps.gsa.widget.PREINSTALLED", true)
+ return opts
+ }
+
+ companion object {
+ private const val SMART_SPACE_WIDGET_HOST_ID = 1027
+ }
+
+ init {
+ mKeyWidgetId = "smart_space_widget_id"
+ }
+ }
+
+ companion object {
+ const val WIDGET_PACKAGE_NAME = "com.google.android.googlequicksearchbox"
+ private const val WIDGET_CLASS_NAME = "com.google.android.apps.gsa.staticplugins.smartspace.widget.SmartspaceWidgetProvider"
+ fun getDateView(parent: ViewGroup): View {
+ return LayoutInflater.from(parent.context)
+ .inflate(R.layout.smart_space_date_view, parent, false)
+ }
+ }
+}