Merge "App discovery integration in All Apps search" into ub-launcher3-dorval

This commit is contained in:
Mario Bertschler
2017-03-04 00:05:39 +00:00
committed by Android (Google) Code Review
19 changed files with 798 additions and 85 deletions
+43
View File
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2017 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="@dimen/badge_size"
android:height="@dimen/badge_size"
android:viewportWidth="18"
android:viewportHeight="18">
<path
android:fillColor="@android:color/black"
android:fillType="evenOdd"
android:strokeWidth="1"
android:pathData="M 9 0 C 13.9705627485 0 18 4.02943725152 18 9 C 18 13.9705627485 13.9705627485 18 9 18 C 4.02943725152 18 0 13.9705627485 0 9 C 0 4.02943725152 4.02943725152 0 9 0 Z" />
<path
android:fillColor="@android:color/white"
android:fillType="evenOdd"
android:strokeWidth="1"
android:pathData="M 9 0 C 13.9705627485 0 18 4.02943725152 18 9 C 18 13.9705627485 13.9705627485 18 9 18 C 4.02943725152 18 0 13.9705627485 0 9 C 0 4.02943725152 4.02943725152 0 9 0 Z" />
<path
android:fillColor="@android:color/white"
android:fillType="evenOdd"
android:strokeWidth="1"
android:pathData="M 9 0 C 13.9705627485 0 18 4.02943725152 18 9 C 18 13.9705627485 13.9705627485 18 9 18 C 4.02943725152 18 0 13.9705627485 0 9 C 0 4.02943725152 4.02943725152 0 9 0 Z" />
<path
android:fillColor="@android:color/black"
android:fillAlpha="0.87"
android:fillType="evenOdd"
android:strokeWidth="1"
android:pathData="M 6 10.4123279 L 8.63934949 10.4123279 L 8.63934949 15.6 L 12.5577168 7.84517705 L 9.94547194 7.84517705 L 9.94547194 2 Z" />
</vector>
+27
View File
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2017 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="12dp"
android:height="12dp"
android:viewportWidth="12"
android:viewportHeight="12">
<path
android:fillColor="#FB8C00"
android:fillType="evenOdd"
android:strokeWidth="1"
android:pathData="M 9.76511755 11.9348136 L 8.33665684 7.16088817 L 12.080006 4.41656311 L 7.49967039 4.41856896 L 6.03138903 0 L 4.57932894 4.41856896 L -1.34115008e-16 4.41656311 L 3.72612122 7.16088817 L 2.29967385 11.9348136 L 6.03138903 8.82574452 Z" />
</vector>
+111
View File
@@ -0,0 +1,111 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2017 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.
-->
<com.android.launcher3.discovery.AppDiscoveryItemView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:background="?android:selectableItemBackground">
<ImageView
android:id="@+id/image"
android:layout_width="56dp"
android:layout_height="56dp"
android:padding="8dp"
android:scaleType="fitCenter"/>
<ImageView
android:id="@+id/badge"
android:layout_width="16dp"
android:layout_height="16dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_instant_app"
android:layout_marginRight="6dp"
android:layout_marginBottom="6dp"
android:layout_alignRight="@+id/image"
android:layout_alignBottom="@+id/image"
android:clickable="false"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_centerVertical="true"
android:layout_toRightOf="@id/image">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="?android:textColorSecondary"
android:textSize="15sp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/rating"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?android:textColorSecondary"
android:textSize="14sp"
android:layout_gravity="center_vertical"/>
<com.android.launcher3.discovery.RatingView
android:id="@+id/rating_view"
android:layout_width="80dp"
android:layout_height="16dp"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:layout_gravity="center_vertical"/>
<TextView
android:id="@+id/review_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:textColor="?android:textColorHint"
android:textSize="14sp"
android:layout_gravity="center_vertical"/>
<Space
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1"/>
<TextView
android:id="@+id/price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?android:textColorHint"
android:textSize="14sp"
android:layout_marginRight="12dp"
android:textAllCaps="true"/>
</LinearLayout>
</LinearLayout>
<ImageView
android:importantForAccessibility="no"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:paddingLeft="@dimen/container_fastscroll_thumb_max_width"
android:paddingRight="@dimen/container_fastscroll_thumb_max_width"
android:src="@drawable/all_apps_divider"
android:scaleType="fitXY"
android:focusable="false" />
</com.android.launcher3.discovery.AppDiscoveryItemView>
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2017 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.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="20dp"
android:paddingLeft="@dimen/container_fastscroll_thumb_max_width"
android:paddingRight="@dimen/container_fastscroll_thumb_max_width">
<ProgressBar
android:id="@+id/loadingProgressBar"
style="@android:style/Widget.Material.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="6dp"
android:maxHeight="6dp"
android:indeterminate="true"
android:layout_gravity="center"/>
<View
android:id="@+id/loadedDivider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@drawable/all_apps_divider"
android:layout_gravity="center"
android:visibility="invisible"/>
</FrameLayout>
+1 -5
View File
@@ -21,14 +21,11 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.LauncherActivityInfo;
import android.os.UserHandle;
import android.util.Log;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.PackageManagerHelper;
import java.util.ArrayList;
/**
* Represents an app in AllAppsView.
*/
@@ -44,7 +41,7 @@ public class AppInfo extends ItemInfoWithIcon {
/**
* {@see ShortcutInfo#isDisabled}
*/
int isDisabled = ShortcutInfo.DEFAULT;
public int isDisabled = ShortcutInfo.DEFAULT;
public AppInfo() {
itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT;
@@ -83,7 +80,6 @@ public class AppInfo extends ItemInfoWithIcon {
title = Utilities.trim(info.title);
intent = new Intent(info.intent);
isDisabled = info.isDisabled;
iconBitmap = info.iconBitmap;
}
@Override
@@ -35,7 +35,9 @@ public abstract class ItemInfoWithIcon extends ItemInfo {
protected ItemInfoWithIcon() { }
protected ItemInfoWithIcon(ItemInfo info) {
protected ItemInfoWithIcon(ItemInfoWithIcon info) {
super(info);
iconBitmap = info.iconBitmap;
usingLowResIcon = info.usingLowResIcon;
}
}
+5 -3
View File
@@ -1059,6 +1059,7 @@ public class Launcher extends BaseActivity
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onResume();
}
}
@Override
@@ -2459,7 +2460,7 @@ public class Launcher extends BaseActivity
throw new IllegalArgumentException("Input must have a valid intent");
}
boolean success = startActivitySafely(v, intent, item);
getUserEventDispatcher().logAppLaunch(v, intent);
getUserEventDispatcher().logAppLaunch(v, intent); // TODO for discovered apps b/35802115
if (success && v instanceof BubbleTextView) {
mWaitingForResume = (BubbleTextView) v;
@@ -2708,9 +2709,10 @@ public class Launcher extends BaseActivity
intent.setSourceBounds(getViewBounds(v));
}
try {
if (Utilities.ATLEAST_MARSHMALLOW && item != null
if (Utilities.ATLEAST_MARSHMALLOW
&& (item instanceof ShortcutInfo)
&& (item.itemType == Favorites.ITEM_TYPE_SHORTCUT
|| item.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT)
|| item.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT)
&& !((ShortcutInfo) item).isPromise()) {
// Shortcuts need some special checks due to legacy reasons.
startShortcutIntentSafely(intent, optsBundle, item);
@@ -134,7 +134,6 @@ public class ShortcutInfo extends ItemInfoWithIcon {
title = info.title;
intent = new Intent(info.intent);
iconResource = info.iconResource;
iconBitmap = info.iconBitmap;
status = info.status;
mInstallProgress = info.mInstallProgress;
isDisabled = info.isDisabled;
@@ -146,8 +145,6 @@ public class ShortcutInfo extends ItemInfoWithIcon {
title = Utilities.trim(info.title);
intent = new Intent(info.intent);
isDisabled = info.isDisabled;
iconBitmap = info.iconBitmap;
usingLowResIcon = info.usingLowResIcon;
}
/**
@@ -20,6 +20,8 @@ import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.InsetDrawable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.text.Selection;
import android.text.Spannable;
@@ -46,6 +48,8 @@ import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.discovery.AppDiscoveryItem;
import com.android.launcher3.discovery.AppDiscoveryUpdateState;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.folder.Folder;
@@ -211,7 +215,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
// IF scroller is at the very top OR there is no scroll bar because there is probably not
// enough items to scroll, THEN it's okay for the container to be pulled down.
if (mAppsRecyclerView.getScrollBar().getThumbOffsetY() <= 0) {
if (mAppsRecyclerView.getCurrentScrollY() == 0) {
return true;
}
return false;
@@ -425,13 +429,21 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
@Override
public void onSearchResult(String query, ArrayList<ComponentKey> apps) {
if (apps != null) {
if (mApps.setOrderedFilter(apps)) {
mAppsRecyclerView.onSearchResultsChanged();
}
mApps.setOrderedFilter(apps);
mAppsRecyclerView.onSearchResultsChanged();
mAdapter.setLastSearchQuery(query);
}
}
@Override
public void onAppDiscoverySearchUpdate(@Nullable AppDiscoveryItem app,
@NonNull AppDiscoveryUpdateState state) {
if (!mLauncher.isDestroyed()) {
mApps.onAppDiscoverySearchUpdate(app, state);
mAppsRecyclerView.onSearchResultsChanged();
}
}
@Override
public void clearSearchResult() {
if (mApps.setOrderedFilter(null)) {
@@ -33,12 +33,13 @@ import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.widget.TextView;
import com.android.launcher3.discovery.AppDiscoveryAppInfo;
import com.android.launcher3.AppInfo;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.allapps.AlphabeticalAppsList.AdapterItem;
import com.android.launcher3.discovery.AppDiscoveryItemView;
import java.util.List;
@@ -67,6 +68,8 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
public static final int VIEW_TYPE_SEARCH_DIVIDER = 1 << 6;
// The divider that separates prediction icons from the app list
public static final int VIEW_TYPE_PREDICTION_DIVIDER = 1 << 7;
public static final int VIEW_TYPE_APPS_LOADING_DIVIDER = 1 << 8;
public static final int VIEW_TYPE_DISCOVERY_ITEM = 1 << 9;
// Common view type masks
public static final int VIEW_TYPE_MASK_DIVIDER = VIEW_TYPE_SEARCH_DIVIDER
@@ -74,6 +77,8 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
| VIEW_TYPE_PREDICTION_DIVIDER;
public static final int VIEW_TYPE_MASK_ICON = VIEW_TYPE_ICON
| VIEW_TYPE_PREDICTION_ICON;
public static final int VIEW_TYPE_MASK_CONTENT = VIEW_TYPE_MASK_ICON
| VIEW_TYPE_DISCOVERY_ITEM;
public interface BindViewCallback {
@@ -84,7 +89,6 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
* ViewHolder for each icon.
*/
public static class ViewHolder extends RecyclerView.ViewHolder {
public ViewHolder(View v) {
super(v);
}
@@ -150,7 +154,7 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
adapterPosition = Math.max(adapterPosition, mApps.getAdapterItems().size() - 1);
int extraRows = 0;
for (int i = 0; i <= adapterPosition; i++) {
if ((items.get(i).viewType & VIEW_TYPE_MASK_ICON) == 0) {
if (!isViewType(items.get(i).viewType, VIEW_TYPE_MASK_CONTENT)) {
extraRows++;
}
}
@@ -273,8 +277,7 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case VIEW_TYPE_ICON:
/* falls through */
case VIEW_TYPE_PREDICTION_ICON: {
case VIEW_TYPE_PREDICTION_ICON:
BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
R.layout.all_apps_icon, parent, false);
icon.setOnClickListener(mIconClickListener);
@@ -284,14 +287,14 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
icon.setOnFocusChangeListener(mIconFocusListener);
// Ensure the all apps icon height matches the workspace icons
DeviceProfile profile = mLauncher.getDeviceProfile();
Point cellSize = profile.getCellSize();
GridLayoutManager.LayoutParams lp =
(GridLayoutManager.LayoutParams) icon.getLayoutParams();
lp.height = cellSize.y;
icon.setLayoutParams(lp);
icon.getLayoutParams().height = getCellSize().y;
return new ViewHolder(icon);
}
case VIEW_TYPE_DISCOVERY_ITEM:
AppDiscoveryItemView appDiscoveryItemView = (AppDiscoveryItemView) mLayoutInflater
.inflate(R.layout.all_apps_discovery_item, parent, false);
appDiscoveryItemView.init(mIconClickListener, mLauncher.getAccessibilityDelegate(),
mIconLongClickListener);
return new ViewHolder(appDiscoveryItemView);
case VIEW_TYPE_EMPTY_SEARCH:
return new ViewHolder(mLayoutInflater.inflate(R.layout.all_apps_empty_search,
parent, false));
@@ -308,8 +311,11 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
case VIEW_TYPE_SEARCH_DIVIDER:
return new ViewHolder(mLayoutInflater.inflate(
R.layout.all_apps_search_divider, parent, false));
case VIEW_TYPE_APPS_LOADING_DIVIDER:
View loadingDividerView = mLayoutInflater.inflate(
R.layout.all_apps_discovery_loading_divider, parent, false);
return new ViewHolder(loadingDividerView);
case VIEW_TYPE_PREDICTION_DIVIDER:
/* falls through */
case VIEW_TYPE_SEARCH_MARKET_DIVIDER:
return new ViewHolder(mLayoutInflater.inflate(
R.layout.all_apps_divider, parent, false));
@@ -318,23 +324,26 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
}
}
private Point getCellSize() {
return mLauncher.getDeviceProfile().getCellSize();
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
switch (holder.getItemViewType()) {
case VIEW_TYPE_ICON: {
case VIEW_TYPE_ICON:
case VIEW_TYPE_PREDICTION_ICON:
AppInfo info = mApps.getAdapterItems().get(position).appInfo;
BubbleTextView icon = (BubbleTextView) holder.itemView;
icon.applyFromApplicationInfo(info);
icon.setAccessibilityDelegate(mLauncher.getAccessibilityDelegate());
break;
}
case VIEW_TYPE_PREDICTION_ICON: {
AppInfo info = mApps.getAdapterItems().get(position).appInfo;
BubbleTextView icon = (BubbleTextView) holder.itemView;
icon.applyFromApplicationInfo(info);
icon.setAccessibilityDelegate(mLauncher.getAccessibilityDelegate());
case VIEW_TYPE_DISCOVERY_ITEM:
AppDiscoveryAppInfo appDiscoveryAppInfo = (AppDiscoveryAppInfo)
mApps.getAdapterItems().get(position).appInfo;
AppDiscoveryItemView view = (AppDiscoveryItemView) holder.itemView;
view.apply(appDiscoveryAppInfo);
break;
}
case VIEW_TYPE_EMPTY_SEARCH:
TextView emptyViewText = (TextView) holder.itemView;
emptyViewText.setText(mEmptySearchMessage);
@@ -349,6 +358,15 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
searchView.setVisibility(View.GONE);
}
break;
case VIEW_TYPE_APPS_LOADING_DIVIDER:
int visLoading = mApps.isAppDiscoveryRunning() ? View.VISIBLE : View.GONE;
int visLoaded = !mApps.isAppDiscoveryRunning() ? View.VISIBLE : View.GONE;
holder.itemView.findViewById(R.id.loadingProgressBar).setVisibility(visLoading);
holder.itemView.findViewById(R.id.loadedDivider).setVisibility(visLoaded);
break;
case VIEW_TYPE_SEARCH_MARKET_DIVIDER:
// nothing to do
break;
}
if (mBindViewCallback != null) {
mBindViewCallback.onBindView(holder);
@@ -30,6 +30,7 @@ import com.android.launcher3.BubbleTextView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.graphics.DrawableFactory;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -110,44 +111,40 @@ public class AllAppsRecyclerView extends BaseRecyclerView {
* all the different view types.
*/
public void preMeasureViews(AllAppsGridAdapter adapter) {
View icon = adapter.onCreateViewHolder(this, AllAppsGridAdapter.VIEW_TYPE_ICON).itemView;
final int iconHeight = icon.getLayoutParams().height;
mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_ICON, iconHeight);
mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_PREDICTION_ICON, iconHeight);
final int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(
getResources().getDisplayMetrics().widthPixels, View.MeasureSpec.AT_MOST);
final int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(
getResources().getDisplayMetrics().heightPixels, View.MeasureSpec.AT_MOST);
// Icons
BubbleTextView icon = (BubbleTextView) adapter.onCreateViewHolder(this,
AllAppsGridAdapter.VIEW_TYPE_ICON).itemView;
int iconHeight = icon.getLayoutParams().height;
mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_ICON, iconHeight);
mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_PREDICTION_ICON, iconHeight);
putSameHeightFor(adapter, widthMeasureSpec, heightMeasureSpec,
AllAppsGridAdapter.VIEW_TYPE_PREDICTION_DIVIDER,
AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET_DIVIDER);
putSameHeightFor(adapter, widthMeasureSpec, heightMeasureSpec,
AllAppsGridAdapter.VIEW_TYPE_SEARCH_DIVIDER);
putSameHeightFor(adapter, widthMeasureSpec, heightMeasureSpec,
AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET);
putSameHeightFor(adapter, widthMeasureSpec, heightMeasureSpec,
AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH);
// Search divider
View searchDivider = adapter.onCreateViewHolder(this,
AllAppsGridAdapter.VIEW_TYPE_SEARCH_DIVIDER).itemView;
searchDivider.measure(widthMeasureSpec, heightMeasureSpec);
int searchDividerHeight = searchDivider.getMeasuredHeight();
mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_SEARCH_DIVIDER, searchDividerHeight);
if (FeatureFlags.DISCOVERY_ENABLED) {
putSameHeightFor(adapter, widthMeasureSpec, heightMeasureSpec,
AllAppsGridAdapter.VIEW_TYPE_APPS_LOADING_DIVIDER);
putSameHeightFor(adapter, widthMeasureSpec, heightMeasureSpec,
AllAppsGridAdapter.VIEW_TYPE_DISCOVERY_ITEM);
}
}
// Generic dividers
View divider = adapter.onCreateViewHolder(this,
AllAppsGridAdapter.VIEW_TYPE_PREDICTION_DIVIDER).itemView;
divider.measure(widthMeasureSpec, heightMeasureSpec);
int dividerHeight = divider.getMeasuredHeight();
mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_PREDICTION_DIVIDER, dividerHeight);
mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET_DIVIDER, dividerHeight);
// Search views
View emptySearch = adapter.onCreateViewHolder(this,
AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH).itemView;
emptySearch.measure(widthMeasureSpec, heightMeasureSpec);
mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH,
emptySearch.getMeasuredHeight());
View searchMarket = adapter.onCreateViewHolder(this,
AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET).itemView;
searchMarket.measure(widthMeasureSpec, heightMeasureSpec);
mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET,
searchMarket.getMeasuredHeight());
private void putSameHeightFor(AllAppsGridAdapter adapter, int w, int h, int... viewTypes) {
View view = adapter.onCreateViewHolder(this, viewTypes[0]).itemView;
view.measure(w, h);
for (int viewType : viewTypes) {
mViewHeights.put(viewType, view.getMeasuredHeight());
}
}
/**
@@ -207,7 +204,7 @@ public class AllAppsRecyclerView extends BaseRecyclerView {
// Always scroll the view to the top so the user can see the changed results
scrollToTop();
if (mApps.hasNoFilteredResults()) {
if (mApps.shouldShowEmptySearch()) {
if (mEmptySearchBackground == null) {
mEmptySearchBackground = DrawableFactory.get(getContext())
.getAllAppsBackground(getContext());
@@ -438,4 +435,5 @@ public class AllAppsRecyclerView extends BaseRecyclerView {
x + mEmptySearchBackground.getIntrinsicWidth(),
y + mEmptySearchBackground.getIntrinsicHeight());
}
}
@@ -19,6 +19,8 @@ import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
@@ -32,6 +34,8 @@ import android.widget.TextView.OnEditorActionListener;
import com.android.launcher3.ExtendedEditText;
import com.android.launcher3.Launcher;
import com.android.launcher3.Utilities;
import com.android.launcher3.discovery.AppDiscoveryItem;
import com.android.launcher3.discovery.AppDiscoveryUpdateState;
import com.android.launcher3.util.ComponentKey;
import java.util.ArrayList;
@@ -46,7 +50,7 @@ public abstract class AllAppsSearchBarController
protected AlphabeticalAppsList mApps;
protected Callbacks mCb;
protected ExtendedEditText mInput;
private String mQuery;
protected String mQuery;
protected DefaultAppSearchAlgorithm mSearchAlgorithm;
protected InputMethodManager mInputMethodManager;
@@ -73,6 +77,14 @@ public abstract class AllAppsSearchBarController
mInput.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
mSearchAlgorithm = onInitializeSearch();
onInitialized();
}
/**
* You can override this method to perform custom initialization.
*/
protected void onInitialized() {
}
/**
@@ -117,6 +129,7 @@ public abstract class AllAppsSearchBarController
if (actionId != EditorInfo.IME_ACTION_SEARCH) {
return false;
}
// Skip if the query is empty
String query = v.getText().toString();
if (query.isEmpty()) {
@@ -206,5 +219,19 @@ public abstract class AllAppsSearchBarController
* Called when the search results should be cleared.
*/
void clearSearchResult();
/**
* Called when the app discovery is providing an update of search, which can either be
* START for starting a new discovery,
* UPDATE for providing a new search result, can be called multiple times,
* END for indicating the end of results.
*
* @param app result item if UPDATE, else null
* @param app the update state, START, UPDATE or END
*/
void onAppDiscoverySearchUpdate(@Nullable AppDiscoveryItem app,
@NonNull AppDiscoveryUpdateState state);
}
}
@@ -17,12 +17,17 @@ package com.android.launcher3.allapps;
import android.content.Context;
import android.os.Process;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import com.android.launcher3.AppInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.compat.AlphabeticIndexCompat;
import com.android.launcher3.config.ProviderConfig;
import com.android.launcher3.discovery.AppDiscoveryAppInfo;
import com.android.launcher3.discovery.AppDiscoveryItem;
import com.android.launcher3.discovery.AppDiscoveryUpdateState;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.LabelComparator;
@@ -48,6 +53,8 @@ public class AlphabeticalAppsList {
private final int mFastScrollDistributionMode = FAST_SCROLL_FRACTION_DISTRIBUTE_BY_NUM_SECTIONS;
private AppDiscoveryUpdateState mAppDiscoveryUpdateState;
/**
* Info about a fast scroller section, depending if sections are merged, the fast scroller
* sections will not be the same set as the section headers.
@@ -106,6 +113,17 @@ public class AlphabeticalAppsList {
return item;
}
public static AdapterItem asDiscoveryItem(int pos, String sectionName, AppInfo appInfo,
int appIndex) {
AdapterItem item = new AdapterItem();
item.viewType = AllAppsGridAdapter.VIEW_TYPE_DISCOVERY_ITEM;
item.position = pos;
item.sectionName = sectionName;
item.appInfo = appInfo;
item.appIndex = appIndex;
return item;
}
public static AdapterItem asEmptySearch(int pos) {
AdapterItem item = new AdapterItem();
item.viewType = AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH;
@@ -134,6 +152,13 @@ public class AlphabeticalAppsList {
return item;
}
public static AdapterItem asLoadingDivider(int pos) {
AdapterItem item = new AdapterItem();
item.viewType = AllAppsGridAdapter.VIEW_TYPE_APPS_LOADING_DIVIDER;
item.position = pos;
return item;
}
public static AdapterItem asMarketSearch(int pos) {
AdapterItem item = new AdapterItem();
item.viewType = AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET;
@@ -142,22 +167,24 @@ public class AlphabeticalAppsList {
}
}
private Launcher mLauncher;
private final Launcher mLauncher;
// The set of apps from the system not including predictions
private final List<AppInfo> mApps = new ArrayList<>();
private final HashMap<ComponentKey, AppInfo> mComponentToAppMap = new HashMap<>();
// The set of filtered apps with the current filter
private List<AppInfo> mFilteredApps = new ArrayList<>();
private final List<AppInfo> mFilteredApps = new ArrayList<>();
// The current set of adapter items
private List<AdapterItem> mAdapterItems = new ArrayList<>();
private final ArrayList<AdapterItem> mAdapterItems = new ArrayList<>();
// The set of sections that we allow fast-scrolling to (includes non-merged sections)
private List<FastScrollSectionInfo> mFastScrollerSections = new ArrayList<>();
private final List<FastScrollSectionInfo> mFastScrollerSections = new ArrayList<>();
// The set of predicted app component names
private List<ComponentKey> mPredictedAppComponents = new ArrayList<>();
private final List<ComponentKey> mPredictedAppComponents = new ArrayList<>();
// The set of predicted apps resolved from the component names and the current set of apps
private List<AppInfo> mPredictedApps = new ArrayList<>();
private final List<AppInfo> mPredictedApps = new ArrayList<>();
private final List<AppDiscoveryAppInfo> mDiscoveredApps = new ArrayList<>();
// The of ordered component names as a result of a search query
private ArrayList<ComponentKey> mSearchResults;
private HashMap<CharSequence, String> mCachedSectionNames = new HashMap<>();
@@ -240,6 +267,10 @@ public class AlphabeticalAppsList {
return (mSearchResults != null) && mFilteredApps.isEmpty();
}
boolean shouldShowEmptySearch() {
return hasNoFilteredResults() && !isAppDiscoveryRunning() && mDiscoveredApps.isEmpty();
}
/**
* Sets the sorted list of filtered components.
*/
@@ -253,6 +284,20 @@ public class AlphabeticalAppsList {
return false;
}
public void onAppDiscoverySearchUpdate(@Nullable AppDiscoveryItem app,
@NonNull AppDiscoveryUpdateState state) {
mAppDiscoveryUpdateState = state;
switch (state) {
case START:
mDiscoveredApps.clear();
break;
case UPDATE:
mDiscoveredApps.add(new AppDiscoveryAppInfo(app, mLauncher));
break;
}
updateAdapterItems();
}
/**
* Sets the current set of predicted apps. Since this can be called before we get the full set
* of applications, we should merge the results only in onAppsUpdated() which is idempotent.
@@ -350,6 +395,17 @@ public class AlphabeticalAppsList {
* mCachedSectionNames to have been calculated for the set of all apps in mApps.
*/
private void updateAdapterItems() {
refillAdapterItems();
refreshRecyclerView();
}
private void refreshRecyclerView() {
if (mAdapter != null) {
mAdapter.notifyDataSetChanged();
}
}
private void refillAdapterItems() {
String lastSectionName = null;
FastScrollSectionInfo lastFastScrollerSectionInfo = null;
int position = 0;
@@ -435,14 +491,30 @@ public class AlphabeticalAppsList {
mFilteredApps.add(info);
}
// Append the search market item if we are currently searching
if (hasFilter()) {
if (hasNoFilteredResults()) {
mAdapterItems.add(AdapterItem.asEmptySearch(position++));
if (isAppDiscoveryRunning() || mDiscoveredApps.size() > 0) {
mAdapterItems.add(AdapterItem.asLoadingDivider(position++));
// Append all app discovery results
for (int i = 0; i < mDiscoveredApps.size(); i++) {
AppDiscoveryAppInfo appDiscoveryAppInfo = mDiscoveredApps.get(i);
AdapterItem item = AdapterItem.asDiscoveryItem(position++,
"", appDiscoveryAppInfo, appIndex++);
mAdapterItems.add(item);
}
if (!isAppDiscoveryRunning()) {
mAdapterItems.add(AdapterItem.asMarketSearch(position++));
}
} else {
mAdapterItems.add(AdapterItem.asMarketDivider(position++));
// Append the search market item
if (hasNoFilteredResults()) {
mAdapterItems.add(AdapterItem.asEmptySearch(position++));
} else {
mAdapterItems.add(AdapterItem.asMarketDivider(position++));
}
mAdapterItems.add(AdapterItem.asMarketSearch(position++));
}
mAdapterItems.add(AdapterItem.asMarketSearch(position++));
}
if (mNumAppsPerRow != 0) {
@@ -498,11 +570,11 @@ public class AlphabeticalAppsList {
break;
}
}
}
// Refresh the recycler view
if (mAdapter != null) {
mAdapter.notifyDataSetChanged();
}
public boolean isAppDiscoveryRunning() {
return mAppDiscoveryUpdateState == AppDiscoveryUpdateState.START
|| mAppDiscoveryUpdateState == AppDiscoveryUpdateState.UPDATE;
}
private List<AppInfo> getFiltersAppInfos() {
@@ -532,4 +604,5 @@ public class AlphabeticalAppsList {
}
return sectionName;
}
}
@@ -0,0 +1,88 @@
/*
* Copyright (C) 2017 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.discovery;
import android.content.ComponentName;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.android.launcher3.AppInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
import com.android.launcher3.ShortcutInfo;
public class AppDiscoveryAppInfo extends AppInfo {
private final @NonNull Launcher mLauncher;
public final boolean showAsDiscoveryItem;
public final boolean isInstantApp;
public final float rating;
public final long reviewCount;
public final @NonNull String publisher;
public final @NonNull Intent installIntent;
public final @NonNull Intent launchIntent;
public final @Nullable String priceFormatted;
public AppDiscoveryAppInfo(AppDiscoveryItem item, Launcher launcher) {
this.mLauncher = launcher;
this.intent = item.isInstantApp ? item.launchIntent : item.installIntent;
this.title = item.title;
this.iconBitmap = item.bitmap;
this.isDisabled = ShortcutInfo.DEFAULT;
this.usingLowResIcon = false;
this.isInstantApp = item.isInstantApp;
this.rating = item.starRating;
this.showAsDiscoveryItem = true;
this.publisher = item.publisher != null ? item.publisher : "";
this.priceFormatted = item.price;
this.componentName = new ComponentName(item.packageName, "");
this.installIntent = item.installIntent;
this.launchIntent = item.launchIntent;
this.reviewCount = item.reviewCount;
this.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
}
@Override
public ShortcutInfo makeShortcut() {
if (!isDragAndDropSupported()) {
throw new RuntimeException("DnD is currently not supported for discovered store apps");
}
ShortcutInfo shortcutInfo = super.makeShortcut();
if (isInstantApp) {
int iconSize = iconBitmap.getWidth();
int badgeSize = mLauncher.getResources().getDimensionPixelOffset(R.dimen.badge_size);
Bitmap icon = Bitmap.createBitmap(iconBitmap);
Drawable badgeDrawable = mLauncher.getDrawable(R.drawable.ic_instant_app);
badgeDrawable.setBounds(iconSize - badgeSize, iconSize - badgeSize, iconSize, iconSize);
Canvas canvas = new Canvas(icon);
badgeDrawable.draw(canvas);
shortcutInfo.iconBitmap = icon;
}
return shortcutInfo;
}
public boolean isDragAndDropSupported() {
return isInstantApp;
}
}
@@ -0,0 +1,62 @@
/*
* Copyright (C) 2017 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.discovery;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
/**
* This class represents the model for a discovered app via app discovery.
* It holds all information for one result retrieved from an app discovery service.
*/
public class AppDiscoveryItem {
public final String packageName;
public final boolean isInstantApp;
public final float starRating;
public final long reviewCount;
public final Intent launchIntent;
public final Intent installIntent;
public final CharSequence title;
public final String publisher;
public final String price;
public final Bitmap bitmap;
public AppDiscoveryItem(String packageName,
boolean isInstantApp,
float starRating,
long reviewCount,
CharSequence title,
String publisher,
Bitmap bitmap,
String price,
Intent launchIntent,
Intent installIntent) {
this.packageName = packageName;
this.isInstantApp = isInstantApp;
this.starRating = starRating;
this.reviewCount = reviewCount;
this.launchIntent = launchIntent;
this.installIntent = installIntent;
this.title = title;
this.publisher = publisher;
this.price = price;
this.bitmap = bitmap;
}
}
@@ -0,0 +1,100 @@
/*
* Copyright (C) 2017 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.discovery;
import android.content.Context;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.android.launcher3.R;
import java.text.DecimalFormat;
import java.text.NumberFormat;
public class AppDiscoveryItemView extends RelativeLayout {
private static boolean SHOW_REVIEW_COUNT = false;
private ImageView mImage;
private ImageView mBadge;
private TextView mTitle;
private TextView mRatingText;
private RatingView mRatingView;
private TextView mReviewCount;
private TextView mPrice;
private OnLongClickListener mOnLongClickListener;
public AppDiscoveryItemView(Context context) {
this(context, null);
}
public AppDiscoveryItemView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public AppDiscoveryItemView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
this.mImage = (ImageView) findViewById(R.id.image);
this.mBadge = (ImageView) findViewById(R.id.badge);
this.mTitle = (TextView) findViewById(R.id.title);
this.mRatingText = (TextView) findViewById(R.id.rating);
this.mRatingView = (RatingView) findViewById(R.id.rating_view);
this.mPrice = (TextView) findViewById(R.id.price);
this.mReviewCount = (TextView) findViewById(R.id.review_count);
}
public void init(OnClickListener clickListener,
AccessibilityDelegate accessibilityDelegate,
OnLongClickListener onLongClickListener) {
setOnClickListener(clickListener);
mImage.setOnClickListener(clickListener);
setAccessibilityDelegate(accessibilityDelegate);
mOnLongClickListener = onLongClickListener;
}
public void apply(@NonNull AppDiscoveryAppInfo info) {
setTag(info);
mImage.setTag(info);
mImage.setImageBitmap(info.iconBitmap);
mImage.setOnLongClickListener(info.isDragAndDropSupported() ? mOnLongClickListener : null);
mBadge.setVisibility(info.isInstantApp ? View.VISIBLE : View.GONE);
mTitle.setText(info.title);
mPrice.setText(info.priceFormatted != null ? info.priceFormatted : "");
mReviewCount.setVisibility(SHOW_REVIEW_COUNT ? View.VISIBLE : View.GONE);
if (info.rating >= 0) {
mRatingText.setText(new DecimalFormat("#.#").format(info.rating));
mRatingView.setRating(info.rating);
mRatingView.setVisibility(View.VISIBLE);
String reviewCountFormatted = NumberFormat.getInstance().format(info.reviewCount);
mReviewCount.setText("(" + reviewCountFormatted + ")");
} else {
// if we don't have a rating
mRatingView.setVisibility(View.GONE);
mRatingText.setText("");
mReviewCount.setText("");
}
}
}
@@ -0,0 +1,21 @@
/*
* Copyright (C) 2017 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.discovery;
public enum AppDiscoveryUpdateState {
START, UPDATE, END
}
@@ -0,0 +1,94 @@
/*
* Copyright (C) 2017 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.discovery;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.drawable.ClipDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import com.android.launcher3.R;
/**
* A simple rating view that shows stars with a rating from 0-5.
*/
public class RatingView extends View {
private static final float WIDTH_FACTOR = 0.9f;
private static final int MAX_LEVEL = 10000;
private static final int MAX_STARS = 5;
private final Drawable mStarDrawable;
private final int mColorGray;
private final int mColorHighlight;
private float rating;
public RatingView(Context context) {
this(context, null);
}
public RatingView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RatingView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mStarDrawable = getResources().getDrawable(R.drawable.ic_star_rating, null);
mColorGray = 0x1E000000;
mColorHighlight = 0x8A000000;
}
public void setRating(float rating) {
this.rating = Math.min(Math.max(rating, 0), MAX_STARS);
}
@Override
protected void onDraw(Canvas canvas) {
drawStars(canvas, MAX_STARS, mColorGray);
drawStars(canvas, rating, mColorHighlight);
}
private void drawStars(Canvas canvas, float stars, int color) {
int fullWidth = getLayoutParams().width;
int cellWidth = fullWidth / MAX_STARS;
int starWidth = (int) (cellWidth * WIDTH_FACTOR);
int padding = cellWidth - starWidth;
int fullStars = (int) stars;
float partialStarFactor = stars - fullStars;
for (int i = 0; i < fullStars; i++) {
int x = i * cellWidth + padding;
Drawable star = mStarDrawable.getConstantState().newDrawable().mutate();
star.setTint(color);
star.setBounds(x, padding, x + starWidth, padding + starWidth);
star.draw(canvas);
}
if (partialStarFactor > 0f) {
int x = fullStars * cellWidth + padding;
ClipDrawable star = new ClipDrawable(mStarDrawable,
Gravity.LEFT, ClipDrawable.HORIZONTAL);
star.setTint(color);
star.setLevel((int) (MAX_LEVEL * partialStarFactor));
star.setBounds(x, padding, x + starWidth, padding + starWidth);
star.draw(canvas);
}
}
}
@@ -49,4 +49,6 @@ public final class FeatureFlags {
public static final boolean LEGACY_ICON_TREATMENT = false;
// When enabled, adaptive icons would have shadows baked when being stored to icon cache.
public static final boolean ADAPTIVE_ICON_SHADOW = true;
// When enabled, app discovery will be enabled if service is implemented
public static final boolean DISCOVERY_ENABLED = false;
}