From 0fc6f684e730b0137900fe317f2c759c1b3909c7 Mon Sep 17 00:00:00 2001 From: Mario Bertschler Date: Fri, 17 Feb 2017 12:16:13 -0800 Subject: [PATCH] App discovery integration in All Apps search This is the basis for app discovery integration while searching in all apps. This does NOT include binding to the actual service and retrieving results, but instead provides all the UI to show suggested instant apps and apps from a store with star rating and pricing. Change-Id: I1605b52848491acee4ac1d15c0112e6a768363f6 --- res/drawable/ic_instant_app.xml | 43 +++++++ res/drawable/ic_star_rating.xml | 27 +++++ res/layout/all_apps_discovery_item.xml | 111 ++++++++++++++++++ .../all_apps_discovery_loading_divider.xml | 40 +++++++ src/com/android/launcher3/AppInfo.java | 6 +- .../android/launcher3/ItemInfoWithIcon.java | 4 +- src/com/android/launcher3/Launcher.java | 8 +- src/com/android/launcher3/ShortcutInfo.java | 3 - .../allapps/AllAppsContainerView.java | 20 +++- .../launcher3/allapps/AllAppsGridAdapter.java | 60 ++++++---- .../allapps/AllAppsRecyclerView.java | 62 +++++----- .../allapps/AllAppsSearchBarController.java | 29 ++++- .../allapps/AlphabeticalAppsList.java | 103 +++++++++++++--- .../discovery/AppDiscoveryAppInfo.java | 88 ++++++++++++++ .../launcher3/discovery/AppDiscoveryItem.java | 62 ++++++++++ .../discovery/AppDiscoveryItemView.java | 100 ++++++++++++++++ .../discovery/AppDiscoveryUpdateState.java | 21 ++++ .../launcher3/discovery/RatingView.java | 94 +++++++++++++++ .../launcher3/graphics/LauncherIcons.java | 1 - .../launcher3/config/FeatureFlags.java | 2 + 20 files changed, 798 insertions(+), 86 deletions(-) create mode 100644 res/drawable/ic_instant_app.xml create mode 100644 res/drawable/ic_star_rating.xml create mode 100644 res/layout/all_apps_discovery_item.xml create mode 100644 res/layout/all_apps_discovery_loading_divider.xml create mode 100644 src/com/android/launcher3/discovery/AppDiscoveryAppInfo.java create mode 100644 src/com/android/launcher3/discovery/AppDiscoveryItem.java create mode 100644 src/com/android/launcher3/discovery/AppDiscoveryItemView.java create mode 100644 src/com/android/launcher3/discovery/AppDiscoveryUpdateState.java create mode 100644 src/com/android/launcher3/discovery/RatingView.java diff --git a/res/drawable/ic_instant_app.xml b/res/drawable/ic_instant_app.xml new file mode 100644 index 0000000000..be5a3e049e --- /dev/null +++ b/res/drawable/ic_instant_app.xml @@ -0,0 +1,43 @@ + + + + + + + + + diff --git a/res/drawable/ic_star_rating.xml b/res/drawable/ic_star_rating.xml new file mode 100644 index 0000000000..4e34fa33e8 --- /dev/null +++ b/res/drawable/ic_star_rating.xml @@ -0,0 +1,27 @@ + + + + + + \ 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 0000000000..2b21ef5707 --- /dev/null +++ b/res/layout/all_apps_discovery_item.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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 0000000000..c7b5ad23da --- /dev/null +++ b/res/layout/all_apps_discovery_loading_divider.xml @@ -0,0 +1,40 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/com/android/launcher3/AppInfo.java b/src/com/android/launcher3/AppInfo.java index 8bf49c27e5..0ddde73c5f 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 a3d8c6a9d4..1e020e2587 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 595e11ab9e..f9e6f4b902 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 b35dcb716d..f0bb1c0c19 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 0732004d44..cc5fa8ce15 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,13 +429,21 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc @Override public void onSearchResult(String query, ArrayList 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)) { diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java index f35230427c..59cac8d26a 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 mApps = new ArrayList<>(); private final HashMap mComponentToAppMap = new HashMap<>(); // The set of filtered apps with the current filter - private List mFilteredApps = new ArrayList<>(); + private final List mFilteredApps = new ArrayList<>(); // The current set of adapter items - private List mAdapterItems = new ArrayList<>(); + private final ArrayList mAdapterItems = new ArrayList<>(); // The set of sections that we allow fast-scrolling to (includes non-merged sections) - private List mFastScrollerSections = new ArrayList<>(); + private final List mFastScrollerSections = new ArrayList<>(); // The set of predicted app component names - private List mPredictedAppComponents = new ArrayList<>(); + private final List mPredictedAppComponents = new ArrayList<>(); // The set of predicted apps resolved from the component names and the current set of apps - private List mPredictedApps = new ArrayList<>(); + private final List mPredictedApps = new ArrayList<>(); + private final List mDiscoveredApps = new ArrayList<>(); + // The of ordered component names as a result of a search query private ArrayList mSearchResults; private HashMap 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 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 0000000000..50e979aac6 --- /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 0000000000..7c10371d0b --- /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 0000000000..6faad87abd --- /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 0000000000..0700a1023f --- /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 0000000000..8fe63d6ba3 --- /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 5db395be6e..2bbe0a125a 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 358a678412..80eebece2e 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; }