diff options
20 files changed, 799 insertions, 87 deletions
diff --git a/res/drawable/ic_instant_app.xml b/res/drawable/ic_instant_app.xml new file mode 100644 index 000000000..be5a3e049 --- /dev/null +++ b/res/drawable/ic_instant_app.xml @@ -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> diff --git a/res/drawable/ic_star_rating.xml b/res/drawable/ic_star_rating.xml new file mode 100644 index 000000000..4e34fa33e --- /dev/null +++ b/res/drawable/ic_star_rating.xml @@ -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>
\ No newline at end of file diff --git a/res/layout/all_apps_discovery_item.xml b/res/layout/all_apps_discovery_item.xml new file mode 100644 index 000000000..2b21ef570 --- /dev/null +++ b/res/layout/all_apps_discovery_item.xml @@ -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>
\ No newline at end of file diff --git a/res/layout/all_apps_discovery_loading_divider.xml b/res/layout/all_apps_discovery_loading_divider.xml new file mode 100644 index 000000000..c7b5ad23d --- /dev/null +++ b/res/layout/all_apps_discovery_loading_divider.xml @@ -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>
\ No newline at end of file diff --git a/src/com/android/launcher3/AppInfo.java b/src/com/android/launcher3/AppInfo.java index 8bf49c27e..0ddde73c5 100644 --- a/src/com/android/launcher3/AppInfo.java +++ b/src/com/android/launcher3/AppInfo.java @@ -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 diff --git a/src/com/android/launcher3/ItemInfoWithIcon.java b/src/com/android/launcher3/ItemInfoWithIcon.java index a3d8c6a9d..1e020e258 100644 --- a/src/com/android/launcher3/ItemInfoWithIcon.java +++ b/src/com/android/launcher3/ItemInfoWithIcon.java @@ -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; } } diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 595e11ab9..f9e6f4b90 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -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); diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java index b35dcb716..f0bb1c0c1 100644 --- a/src/com/android/launcher3/ShortcutInfo.java +++ b/src/com/android/launcher3/ShortcutInfo.java @@ -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; } /** diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java index 0732004d4..cc5fa8ce1 100644 --- a/src/com/android/launcher3/allapps/AllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java @@ -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,14 +429,22 @@ 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)) { mAppsRecyclerView.onSearchResultsChanged(); diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java index f35230427..59cac8d26 100644 --- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java +++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java @@ -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); diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java index a41d83244..64e2fcb3d 100644 --- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java +++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java @@ -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); + + if (FeatureFlags.DISCOVERY_ENABLED) { + putSameHeightFor(adapter, widthMeasureSpec, heightMeasureSpec, + AllAppsGridAdapter.VIEW_TYPE_APPS_LOADING_DIVIDER); + putSameHeightFor(adapter, widthMeasureSpec, heightMeasureSpec, + AllAppsGridAdapter.VIEW_TYPE_DISCOVERY_ITEM); + } + } - // 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); - - // 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()); } + } diff --git a/src/com/android/launcher3/allapps/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/AllAppsSearchBarController.java index 365ab3185..c7ba3abc6 100644 --- a/src/com/android/launcher3/allapps/AllAppsSearchBarController.java +++ b/src/com/android/launcher3/allapps/AllAppsSearchBarController.java @@ -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); } + }
\ No newline at end of file diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java index 0bfbd3eba..f228470f4 100644 --- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java +++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java @@ -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; } + } diff --git a/src/com/android/launcher3/discovery/AppDiscoveryAppInfo.java b/src/com/android/launcher3/discovery/AppDiscoveryAppInfo.java new file mode 100644 index 000000000..50e979aac --- /dev/null +++ b/src/com/android/launcher3/discovery/AppDiscoveryAppInfo.java @@ -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; + } + +} diff --git a/src/com/android/launcher3/discovery/AppDiscoveryItem.java b/src/com/android/launcher3/discovery/AppDiscoveryItem.java new file mode 100644 index 000000000..7c10371d0 --- /dev/null +++ b/src/com/android/launcher3/discovery/AppDiscoveryItem.java @@ -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; + } + +} diff --git a/src/com/android/launcher3/discovery/AppDiscoveryItemView.java b/src/com/android/launcher3/discovery/AppDiscoveryItemView.java new file mode 100644 index 000000000..6faad87ab --- /dev/null +++ b/src/com/android/launcher3/discovery/AppDiscoveryItemView.java @@ -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(""); + } + } +} diff --git a/src/com/android/launcher3/discovery/AppDiscoveryUpdateState.java b/src/com/android/launcher3/discovery/AppDiscoveryUpdateState.java new file mode 100644 index 000000000..0700a1023 --- /dev/null +++ b/src/com/android/launcher3/discovery/AppDiscoveryUpdateState.java @@ -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 +} diff --git a/src/com/android/launcher3/discovery/RatingView.java b/src/com/android/launcher3/discovery/RatingView.java new file mode 100644 index 000000000..8fe63d6ba --- /dev/null +++ b/src/com/android/launcher3/discovery/RatingView.java @@ -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); + } + } +} diff --git a/src/com/android/launcher3/graphics/LauncherIcons.java b/src/com/android/launcher3/graphics/LauncherIcons.java index 5db395be6..2bbe0a125 100644 --- a/src/com/android/launcher3/graphics/LauncherIcons.java +++ b/src/com/android/launcher3/graphics/LauncherIcons.java @@ -40,7 +40,6 @@ import com.android.launcher3.LauncherAppState; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.config.ProviderConfig; import com.android.launcher3.model.PackageItemInfo; import com.android.launcher3.shortcuts.DeepShortcutManager; import com.android.launcher3.shortcuts.ShortcutInfoCompat; diff --git a/src_config/com/android/launcher3/config/FeatureFlags.java b/src_config/com/android/launcher3/config/FeatureFlags.java index 358a67841..80eebece2 100644 --- a/src_config/com/android/launcher3/config/FeatureFlags.java +++ b/src_config/com/android/launcher3/config/FeatureFlags.java @@ -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; } |