Show settings icon at start of settings results

Bug: 170792963
Test: Manual
Screenshot: https://screenshot.googleplex.com/MVBtDZGtQ3aChwU
Change-Id: I16a28a7bd9e3129b40102e020cd7c0111e6fe29b
diff --git a/res/layout/search_result_icon_row.xml b/res/layout/search_result_icon_row.xml
index b51896e..280bbc9 100644
--- a/res/layout/search_result_icon_row.xml
+++ b/res/layout/search_result_icon_row.xml
@@ -40,13 +40,13 @@
             android:gravity="start|center_vertical"
             android:textAlignment="viewStart"
             android:textColor="?android:attr/textColorPrimary"
-            android:textSize="@dimen/settings_hero_title_size" />
+            android:textSize="@dimen/search_hero_title_size" />
 
         <TextView
             android:layout_width="wrap_content"
             android:id="@+id/desc"
             android:textColor="?android:attr/textColorTertiary"
-            android:textSize="@dimen/settings_hero_subtitle_size"
+            android:textSize="@dimen/search_hero_subtitle_size"
             android:layout_height="wrap_content" />
     </LinearLayout>
 
@@ -57,7 +57,7 @@
         android:layout_height="match_parent"
         android:gravity="start|center_vertical"
         launcher:iconDisplay="shortcut_popup"
-        android:textSize="@dimen/settings_hero_subtitle_size"
+        android:textSize="@dimen/search_hero_subtitle_size"
         launcher:iconSizeOverride="@dimen/deep_shortcut_icon_size"
         launcher:layoutHorizontal="false" />
 
@@ -67,7 +67,7 @@
         android:layout_width="@dimen/deep_shortcut_icon_size"
         android:layout_height="match_parent"
         launcher:iconDisplay="shortcut_popup"
-        android:textSize="@dimen/settings_hero_inline_button_size"
+        android:textSize="@dimen/search_hero_inline_button_size"
         launcher:iconSizeOverride="@dimen/deep_shortcut_icon_size"
         launcher:layoutHorizontal="false" />
 
diff --git a/res/layout/search_result_settings_row.xml b/res/layout/search_result_settings_row.xml
index 19daf34..05f7561 100644
--- a/res/layout/search_result_settings_row.xml
+++ b/res/layout/search_result_settings_row.xml
@@ -18,40 +18,42 @@
     android:background="?android:attr/selectableItemBackground"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:orientation="vertical"
+    android:orientation="horizontal"
     android:gravity="center_vertical"
-    android:padding="4dp"
-    android:minHeight="48dp"
-    android:textColor="?android:attr/textColorPrimary"
-    android:textSize="14sp">
+    android:padding="@dimen/dynamic_grid_cell_padding_x"
+    android:textColor="?android:attr/textColorPrimary">
 
-    <TextView
-        android:id="@+id/title"
-        style="@style/TextTitle"
-        android:layout_width="wrap_content"
+    <View
+        android:layout_width="@dimen/search_settings_icon_size"
+        android:src="@drawable/ic_setting"
+        android:id="@+id/icon"
+        android:layout_height="@dimen/search_settings_icon_size" />
+
+    <LinearLayout
+        android:layout_width="0dp"
+        android:orientation="vertical"
+        android:paddingRight="@dimen/dynamic_grid_cell_padding_x"
+        android:paddingLeft="@dimen/dynamic_grid_cell_padding_x"
         android:layout_height="wrap_content"
-        android:layout_marginBottom="4dp"
-        android:textColor="?android:attr/textColorPrimary"
-        android:textSize="16sp" />
-
-    <TextView
-        android:id="@+id/description"
-        style="@style/TextTitle"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:visibility="gone"
-        android:textColor="?android:attr/textColorPrimary"
-        android:textSize="14sp" />
-
-    <TextView
-        android:id="@+id/breadcrumbs"
-        style="@style/TextTitle"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:visibility="gone"
-        android:alpha=".7"
-        android:textColor="?android:attr/textColorSecondary"
-        android:textSize="14sp" />
+        android:layout_weight="1">
 
 
+        <TextView
+            android:id="@+id/title"
+            style="@style/TextTitle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="@dimen/search_line_spacing"
+            android:textColor="?android:attr/textColorPrimary"
+            android:textSize="@dimen/search_hero_title_size" />
+
+        <TextView
+            android:id="@+id/breadcrumbs"
+            style="@style/TextTitle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:visibility="gone"
+            android:textColor="?android:attr/textColorSecondary"
+            android:textSize="@dimen/search_hero_subtitle_size" />
+    </LinearLayout>
 </com.android.launcher3.views.SearchSettingsRowView>
\ No newline at end of file
diff --git a/res/layout/search_result_slice.xml b/res/layout/search_result_slice.xml
index ea1d49a..24d75e9 100644
--- a/res/layout/search_result_slice.xml
+++ b/res/layout/search_result_slice.xml
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 The Android Open Source Project
+<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2020 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
@@ -13,7 +12,29 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<androidx.slice.widget.SliceView xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.launcher3.views.SearchResultSettingsSlice xmlns:android="http://schemas.android.com/apk/res/android"
+    android:paddingHorizontal="@dimen/dynamic_grid_cell_padding_x"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:paddingHorizontal="4dp" />
\ No newline at end of file
+    android:layout_height="wrap_content">
+
+    <FrameLayout
+        android:layout_width="wrap_content"
+        android:paddingTop="@dimen/search_settings_icon_vertical_offset"
+        android:layout_height="wrap_content">
+
+        <View
+            android:layout_width="@dimen/search_settings_icon_size"
+            android:src="@drawable/ic_setting"
+            android:id="@+id/icon"
+            android:layout_height="@dimen/search_settings_icon_size" />
+    </FrameLayout>
+
+    <androidx.slice.widget.SliceView
+        android:id="@+id/slice"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:layout_marginStart="@dimen/dynamic_grid_cell_padding_x"
+        android:layout_width="0dp" />
+
+</com.android.launcher3.views.SearchResultSettingsSlice>
+
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 5387e1b..7df3f77 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -249,8 +249,11 @@
     <dimen name="bottom_sheet_edu_padding">24dp</dimen>
 
     <!-- Search related -->
-    <dimen name="settings_hero_title_size">16sp</dimen>
-    <dimen name="settings_hero_subtitle_size">15sp</dimen>
-    <dimen name="settings_hero_inline_button_size">12sp</dimen>
+    <dimen name="search_hero_title_size">16sp</dimen>
+    <dimen name="search_hero_subtitle_size">15sp</dimen>
+    <dimen name="search_hero_inline_button_size">12sp</dimen>
+    <dimen name="search_settings_icon_size">36dp</dimen>
+    <dimen name="search_settings_icon_vertical_offset">16dp</dimen>
+    <dimen name="search_line_spacing">4dp</dimen>
 
 </resources>
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 7018f20..434bc14 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -37,7 +37,6 @@
 import androidx.core.view.accessibility.AccessibilityRecordCompat;
 import androidx.recyclerview.widget.GridLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
-import androidx.slice.widget.SliceView;
 
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.BubbleTextView;
@@ -47,7 +46,6 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.util.PackageManagerHelper;
-import com.android.launcher3.views.SearchSliceWrapper;
 import com.android.systemui.plugins.shared.SearchTarget;
 
 import java.util.List;
@@ -462,16 +460,9 @@
                     searchView.setVisibility(View.GONE);
                 }
                 break;
-            case VIEW_TYPE_SEARCH_SLICE:
-                SliceView sliceView = (SliceView) holder.itemView;
-                SearchAdapterItem slicePayload = (SearchAdapterItem) mApps.getAdapterItems().get(
-                        position);
-                SearchTarget searchTarget = slicePayload.getSearchTarget();
-                sliceView.setTag(new SearchSliceWrapper(mLauncher, sliceView, searchTarget));
-
-                break;
             case VIEW_TYPE_SEARCH_CORPUS_TITLE:
             case VIEW_TYPE_SEARCH_ROW_WITH_BUTTON:
+            case VIEW_TYPE_SEARCH_SLICE:
             case VIEW_TYPE_SEARCH_ROW:
             case VIEW_TYPE_SEARCH_ICON:
             case VIEW_TYPE_SEARCH_ICON_ROW:
@@ -496,13 +487,6 @@
         if (holder.itemView instanceof AllAppsSectionDecorator.SelfDecoratingView) {
             ((AllAppsSectionDecorator.SelfDecoratingView) holder.itemView).removeDecoration();
         }
-        if (holder.itemView instanceof SliceView) {
-            SliceView sliceView = (SliceView) holder.itemView;
-            if (sliceView.getTag() instanceof SearchSliceWrapper) {
-                ((SearchSliceWrapper) sliceView.getTag()).destroy();
-            }
-            sliceView.setTag(null);
-        }
     }
 
     @Override
diff --git a/src/com/android/launcher3/views/SearchResultSettingsSlice.java b/src/com/android/launcher3/views/SearchResultSettingsSlice.java
new file mode 100644
index 0000000..2d726e7
--- /dev/null
+++ b/src/com/android/launcher3/views/SearchResultSettingsSlice.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * 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.android.launcher3.views;
+
+import android.content.Context;
+import android.net.Uri;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.lifecycle.LiveData;
+import androidx.slice.Slice;
+import androidx.slice.SliceItem;
+import androidx.slice.widget.EventInfo;
+import androidx.slice.widget.SliceLiveData;
+import androidx.slice.widget.SliceView;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.allapps.search.AllAppsSearchBarController;
+import com.android.launcher3.allapps.search.SearchEventTracker;
+import com.android.systemui.plugins.shared.SearchTarget;
+import com.android.systemui.plugins.shared.SearchTargetEvent;
+
+/**
+ * A slice view wrapper with settings app icon at start
+ */
+public class SearchResultSettingsSlice extends LinearLayout implements
+        AllAppsSearchBarController.SearchTargetHandler, SliceView.OnSliceActionListener {
+
+
+    public static final String TARGET_TYPE_SLICE = "settings_slice";
+
+    private static final String TAG = "SearchSliceController";
+    private static final String URI_EXTRA_KEY = "slice_uri";
+
+    private SliceView mSliceView;
+    private View mIcon;
+    private LiveData<Slice> mSliceLiveData;
+    private SearchTarget mSearchTarget;
+    private Launcher mLauncher;
+
+    public SearchResultSettingsSlice(Context context) {
+        this(context, null, 0);
+    }
+
+    public SearchResultSettingsSlice(Context context,
+            @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public SearchResultSettingsSlice(Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        mLauncher = Launcher.getLauncher(getContext());
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mSliceView = findViewById(R.id.slice);
+        mIcon = findViewById(R.id.icon);
+        SearchSettingsRowView.applySettingsIcon(mLauncher, mIcon);
+    }
+
+    @Override
+    public void applySearchTarget(SearchTarget searchTarget) {
+        reset();
+        mSearchTarget = searchTarget;
+        try {
+            mSliceLiveData = SliceLiveData.fromUri(mLauncher, getSliceUri());
+            mSliceLiveData.observe(mLauncher, mSliceView);
+        } catch (Exception ex) {
+            Log.e(TAG, "unable to bind slice", ex);
+        }
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mSliceView.setOnSliceActionListener(this);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        reset();
+    }
+
+    @Override
+    public void handleSelection(int eventType) {
+        SearchEventTracker.INSTANCE.get(mLauncher).notifySearchTargetEvent(
+                new SearchTargetEvent.Builder(mSearchTarget,
+                        SearchTargetEvent.CHILD_SELECT).build());
+    }
+
+    private void reset() {
+        mSliceView.setOnSliceActionListener(null);
+        if (mSliceLiveData != null) {
+            mSliceLiveData.removeObservers(mLauncher);
+        }
+    }
+
+    @Override
+    public void onSliceAction(@NonNull EventInfo eventInfo, @NonNull SliceItem sliceItem) {
+        handleSelection(SearchTargetEvent.CHILD_SELECT);
+    }
+
+    private Uri getSliceUri() {
+        return mSearchTarget.getExtras().getParcelable(URI_EXTRA_KEY);
+    }
+
+}
diff --git a/src/com/android/launcher3/views/SearchSettingsRowView.java b/src/com/android/launcher3/views/SearchSettingsRowView.java
index f0884f8..160ee65 100644
--- a/src/com/android/launcher3/views/SearchSettingsRowView.java
+++ b/src/com/android/launcher3/views/SearchSettingsRowView.java
@@ -15,8 +15,14 @@
  */
 package com.android.launcher3.views;
 
+import static com.android.launcher3.FastBitmapDrawable.newIcon;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.os.Bundle;
 import android.text.TextUtils;
 import android.util.AttributeSet;
@@ -27,38 +33,41 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.search.AllAppsSearchBarController;
 import com.android.launcher3.allapps.search.SearchEventTracker;
+import com.android.launcher3.model.data.PackageItemInfo;
 import com.android.systemui.plugins.shared.SearchTarget;
 import com.android.systemui.plugins.shared.SearchTargetEvent;
 
 import java.util.ArrayList;
+import java.util.List;
 
 /**
- * A row of tappable TextViews with a breadcrumb for settings search.
+ * A row of clickable TextViews with a breadcrumb for settings search.
  */
 public class SearchSettingsRowView extends LinearLayout implements
         View.OnClickListener, AllAppsSearchBarController.SearchTargetHandler {
 
     public static final String TARGET_TYPE_SETTINGS_ROW = "settings_row";
 
-
+    private View mIconView;
     private TextView mTitleView;
-    private TextView mDescriptionView;
     private TextView mBreadcrumbsView;
     private Intent mIntent;
     private SearchTarget mSearchTarget;
 
 
     public SearchSettingsRowView(@NonNull Context context) {
-        super(context);
+        this(context, null, 0);
     }
 
     public SearchSettingsRowView(@NonNull Context context,
             @Nullable AttributeSet attrs) {
-        super(context, attrs);
+        this(context, attrs, 0);
     }
 
     public SearchSettingsRowView(@NonNull Context context, @Nullable AttributeSet attrs,
@@ -69,10 +78,11 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
+        mIconView = findViewById(R.id.icon);
         mTitleView = findViewById(R.id.title);
-        mDescriptionView = findViewById(R.id.description);
         mBreadcrumbsView = findViewById(R.id.breadcrumbs);
         setOnClickListener(this);
+        applySettingsIcon(Launcher.getLauncher(getContext()), mIconView);
     }
 
     @Override
@@ -81,6 +91,7 @@
         Bundle bundle = searchTarget.getExtras();
         mIntent = bundle.getParcelable("intent");
         showIfAvailable(mTitleView, bundle.getString("title"));
+        mIconView.setContentDescription(bundle.getString("title"));
         ArrayList<String> breadcrumbs = bundle.getStringArrayList("breadcrumbs");
         //TODO: implement RTL friendly breadcrumbs view
         showIfAvailable(mBreadcrumbsView, breadcrumbs != null
@@ -113,4 +124,30 @@
         SearchEventTracker.INSTANCE.get(getContext()).notifySearchTargetEvent(
                 new SearchTargetEvent.Builder(mSearchTarget, eventType).build());
     }
+
+    /**
+     * Requests settings app icon from {@link com.android.launcher3.icons.IconCache} and applies
+     * to to view
+     */
+    public static void applySettingsIcon(Launcher launcher, View view) {
+        LauncherAppState appState = LauncherAppState.getInstance(launcher);
+        MODEL_EXECUTOR.post(() -> {
+            PackageItemInfo packageItemInfo = new PackageItemInfo(getSettingsPackageName(launcher));
+            appState.getIconCache().getTitleAndIconForApp(packageItemInfo, false);
+            MAIN_EXECUTOR.post(() -> {
+                FastBitmapDrawable iconDrawable = newIcon(appState.getContext(), packageItemInfo);
+                view.setBackground(iconDrawable);
+            });
+        });
+    }
+
+    private static String getSettingsPackageName(Launcher launcher) {
+        Intent intent = new Intent(android.provider.Settings.ACTION_SETTINGS);
+        List<ResolveInfo> resolveInfos = launcher.getPackageManager().queryIntentActivities(intent,
+                PackageManager.MATCH_DEFAULT_ONLY);
+        if (resolveInfos.size() == 0) {
+            return "";
+        }
+        return resolveInfos.get(0).activityInfo.packageName;
+    }
 }
diff --git a/src/com/android/launcher3/views/SearchSliceWrapper.java b/src/com/android/launcher3/views/SearchSliceWrapper.java
deleted file mode 100644
index f8a7dc0..0000000
--- a/src/com/android/launcher3/views/SearchSliceWrapper.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * 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.android.launcher3.views;
-
-import android.content.Context;
-import android.net.Uri;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.lifecycle.LiveData;
-import androidx.slice.Slice;
-import androidx.slice.SliceItem;
-import androidx.slice.widget.EventInfo;
-import androidx.slice.widget.SliceLiveData;
-import androidx.slice.widget.SliceView;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.allapps.search.SearchEventTracker;
-import com.android.systemui.plugins.shared.SearchTarget;
-import com.android.systemui.plugins.shared.SearchTargetEvent;
-
-/**
- * A Wrapper class for {@link SliceView} search results
- */
-public class SearchSliceWrapper implements SliceView.OnSliceActionListener {
-
-    public static final String TARGET_TYPE_SLICE = "settings_slice";
-
-    private static final String TAG = "SearchSliceController";
-    private static final String URI_EXTRA_KEY = "slice_uri";
-
-
-    private final Launcher mLauncher;
-    private final SearchTarget mSearchTarget;
-    private final SliceView mSliceView;
-    private LiveData<Slice> mSliceLiveData;
-
-    public SearchSliceWrapper(Context context, SliceView sliceView, SearchTarget searchTarget) {
-        mLauncher = Launcher.getLauncher(context);
-        mSearchTarget = searchTarget;
-        mSliceView = sliceView;
-        sliceView.setOnSliceActionListener(this);
-        try {
-            mSliceLiveData = SliceLiveData.fromUri(mLauncher, getSliceUri());
-            mSliceLiveData.observe((Launcher) mLauncher, sliceView);
-        } catch (Exception ex) {
-            Log.e(TAG, "unable to bind slice", ex);
-        }
-    }
-
-    /**
-     * Unregisters event handlers and removes lifecycle observer
-     */
-    public void destroy() {
-        mSliceView.setOnSliceActionListener(null);
-        mSliceLiveData.removeObservers(mLauncher);
-    }
-
-    @Override
-    public void onSliceAction(@NonNull EventInfo info, @NonNull SliceItem item) {
-        SearchEventTracker.INSTANCE.get(mLauncher).notifySearchTargetEvent(
-                new SearchTargetEvent.Builder(mSearchTarget,
-                        SearchTargetEvent.CHILD_SELECT).build());
-    }
-
-    private Uri getSliceUri() {
-        return mSearchTarget.getExtras().getParcelable(URI_EXTRA_KEY);
-    }
-}